mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-11 05:59:23 -06:00
feat: finished roles edit ui for new permissions management
This commit is contained in:
@@ -6,166 +6,67 @@ describe(RoleUtils.name, () => {
|
||||
let adminSession: UserSession;
|
||||
let regularSession: UserSession;
|
||||
|
||||
const projectId = "project-123";
|
||||
|
||||
beforeEach(() => {
|
||||
adminSession = {
|
||||
roleName: adminRoleName,
|
||||
permissions: [],
|
||||
canAccessBackups: false,
|
||||
canCreateNewApps: false,
|
||||
role: {
|
||||
name: adminRoleName,
|
||||
},
|
||||
} as any;
|
||||
|
||||
// Regular user session without any project permissions by default
|
||||
regularSession = {
|
||||
roleName: "user",
|
||||
permissions: [],
|
||||
canAccessBackups: false,
|
||||
canCreateNewApps: false,
|
||||
role: {
|
||||
name: "User",
|
||||
roleProjectPermissions: [],
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
|
||||
/* describe("isAdmin", () => {
|
||||
it("should return true for admin session", () => {
|
||||
expect(RoleUtils.isAdmin(adminSession)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for non-admin session", () => {
|
||||
expect(RoleUtils.isAdmin(regularSession)).toBe(false);
|
||||
});
|
||||
test("should return true if user is admin", () => {
|
||||
const result = RoleUtils.sessionHasReadAccessToProject(adminSession, projectId);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
describe("getRolePermissionForApp", () => {
|
||||
it("should return READWRITE for admin regardless of permission", () => {
|
||||
expect(RoleUtils.getRolePermissionForApp(adminSession, "app1")).toBe(
|
||||
RolePermissionEnum.READWRITE
|
||||
);
|
||||
});
|
||||
|
||||
it("should return null for non-admin without permission", () => {
|
||||
expect(RoleUtils.getRolePermissionForApp(regularSession, "app1")).toBe(null);
|
||||
});
|
||||
|
||||
it("should return the specific permission for non-admin with permission", () => {
|
||||
regularSession.permissions = [{ appId: "app1", permission: RolePermissionEnum.READ }];
|
||||
expect(RoleUtils.getRolePermissionForApp(regularSession, "app1")).toBe(
|
||||
RolePermissionEnum.READ
|
||||
);
|
||||
});
|
||||
test("should return false if non-admin user has no project permission", () => {
|
||||
const result = RoleUtils.sessionHasReadAccessToProject(regularSession, projectId);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
describe("sessionHasAccessToBackups", () => {
|
||||
it("should return true for admin session", () => {
|
||||
expect(RoleUtils.sessionHasAccessToBackups(adminSession)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for non-admin with backups access", () => {
|
||||
regularSession.canAccessBackups = true;
|
||||
expect(RoleUtils.sessionHasAccessToBackups(regularSession)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for non-admin without backups access", () => {
|
||||
expect(RoleUtils.sessionHasAccessToBackups(regularSession)).toBe(false);
|
||||
});
|
||||
test("should return true if non-admin user has project permission with non-empty roleAppPermissions", () => {
|
||||
regularSession.role!.roleProjectPermissions = [
|
||||
{
|
||||
projectId,
|
||||
roleAppPermissions: [{ appId: "app1", permission: RolePermissionEnum.READ }],
|
||||
readApps: false,
|
||||
},
|
||||
] as any;
|
||||
const result = RoleUtils.sessionHasReadAccessToProject(regularSession, projectId);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
describe("sessionCanCreateNewApps", () => {
|
||||
it("should return true for admin session", () => {
|
||||
expect(RoleUtils.sessionCanCreateNewAppsForProject(adminSession)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for non-admin with ability to create new apps", () => {
|
||||
regularSession.canCreateNewApps = true;
|
||||
expect(RoleUtils.sessionCanCreateNewAppsForProject(regularSession)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for non-admin without ability to create new apps", () => {
|
||||
expect(RoleUtils.sessionCanCreateNewAppsForProject(regularSession)).toBe(false);
|
||||
});
|
||||
test("should return true if non-admin user has project permission with empty roleAppPermissions and readApps true", () => {
|
||||
regularSession.role!.roleProjectPermissions = [
|
||||
{
|
||||
projectId,
|
||||
roleAppPermissions: [],
|
||||
readApps: true,
|
||||
},
|
||||
] as any;
|
||||
const result = RoleUtils.sessionHasReadAccessToProject(regularSession, projectId);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
describe("sessionIsReadOnlyForApp", () => {
|
||||
it("should return false for admin session", () => {
|
||||
expect(RoleUtils.sessionIsReadOnlyForApp(adminSession, "app1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for non-admin with READ permission only", () => {
|
||||
regularSession.permissions = [{ appId: "app1", permission: RolePermissionEnum.READ }];
|
||||
expect(RoleUtils.sessionIsReadOnlyForApp(regularSession, "app1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for non-admin with READWRITE permission", () => {
|
||||
regularSession.permissions = [{ appId: "app1", permission: RolePermissionEnum.READWRITE }];
|
||||
expect(RoleUtils.sessionIsReadOnlyForApp(regularSession, "app1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when no permission is assigned", () => {
|
||||
regularSession.permissions = [];
|
||||
expect(RoleUtils.sessionIsReadOnlyForApp(regularSession, "app1")).toBe(false);
|
||||
});
|
||||
test("should return false if non-admin user has project permission with empty roleAppPermissions and readApps false", () => {
|
||||
regularSession.role!.roleProjectPermissions = [
|
||||
{
|
||||
projectId,
|
||||
roleAppPermissions: [],
|
||||
readApps: false,
|
||||
},
|
||||
] as any;
|
||||
const result = RoleUtils.sessionHasReadAccessToProject(regularSession, projectId);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
describe("sessionHasReadAccessForApp", () => {
|
||||
it("should return true for admin session", () => {
|
||||
expect(RoleUtils.sessionHasReadAccessForApp(adminSession, "app1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for non-admin with READ permission", () => {
|
||||
regularSession.permissions = [{ appId: "app1", permission: RolePermissionEnum.READ }];
|
||||
expect(RoleUtils.sessionHasReadAccessForApp(regularSession, "app1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for non-admin with READWRITE permission", () => {
|
||||
regularSession.permissions = [{ appId: "app1", permission: RolePermissionEnum.READWRITE }];
|
||||
expect(RoleUtils.sessionHasReadAccessForApp(regularSession, "app1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when no permission is granted", () => {
|
||||
regularSession.permissions = [];
|
||||
expect(RoleUtils.sessionHasReadAccessForApp(regularSession, "app1")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sessionHasWriteAccessForApp", () => {
|
||||
it("should return true for admin session", () => {
|
||||
expect(RoleUtils.sessionHasWriteAccessForApp(adminSession, "app1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for non-admin with READWRITE permission", () => {
|
||||
regularSession.permissions = [{ appId: "app1", permission: RolePermissionEnum.READWRITE }];
|
||||
expect(RoleUtils.sessionHasWriteAccessForApp(regularSession, "app1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for non-admin with only READ permission", () => {
|
||||
regularSession.permissions = [{ appId: "app1", permission: RolePermissionEnum.READ }];
|
||||
expect(RoleUtils.sessionHasWriteAccessForApp(regularSession, "app1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when no permission is assigned", () => {
|
||||
regularSession.permissions = [];
|
||||
expect(RoleUtils.sessionHasWriteAccessForApp(regularSession, "app1")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sessionHasReadAccessToProject", () => {
|
||||
it("should return true if project has no apps and session can create new apps", () => {
|
||||
regularSession.canCreateNewApps = true;
|
||||
const project = { apps: [] };
|
||||
expect(RoleUtils.sessionHasReadAccessToProject(regularSession, project)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true if project has no apps and session is admin", () => {
|
||||
const project = { apps: [] };
|
||||
expect(RoleUtils.sessionHasReadAccessToProject(adminSession, project)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true if at least one app grants read access", () => {
|
||||
const project = { apps: [{ id: "app1" }, { id: "app2" }] };
|
||||
regularSession.permissions = [{ appId: "app2", permission: RolePermissionEnum.READ }];
|
||||
expect(RoleUtils.sessionHasReadAccessToProject(regularSession, project)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if no app grants read access", () => {
|
||||
const project = { apps: [{ id: "app1" }] };
|
||||
regularSession.permissions = [];
|
||||
expect(RoleUtils.sessionHasReadAccessToProject(regularSession, project)).toBe(false);
|
||||
});
|
||||
});*/
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,8 +48,11 @@ export const createAppFromTemplate = async (prevState: any, inputData: AppTempla
|
||||
|
||||
export const deleteApp = async (appId: string) =>
|
||||
simpleAction(async () => {
|
||||
await isAuthorizedWriteForApp(appId);
|
||||
const session = await getAuthUserSession();
|
||||
const app = await appService.getExtendedById(appId);
|
||||
if (!RoleUtils.sessionCanDeleteAppsForProject(session, app.projectId)) {
|
||||
throw new ServiceException("You are not allowed to delete apps in this project.");
|
||||
}
|
||||
// First delete external services wich might be running
|
||||
await dbGateService.deleteToolForAppIfExists(appId);
|
||||
await phpMyAdminService.deleteToolForAppIfExists(appId);
|
||||
|
||||
@@ -13,10 +13,19 @@ import { deleteApp } from "./actions";
|
||||
import { useBreadcrumbs, useConfirmDialog } from "@/frontend/states/zustand.states";
|
||||
import { useEffect } from "react";
|
||||
import { EditAppDialog } from "./edit-app-dialog";
|
||||
import { UserSession } from "@/shared/model/sim-session.model";
|
||||
import { RoleUtils } from "@/shared/utils/role.utils";
|
||||
|
||||
|
||||
|
||||
export default function AppTable({ app, projectId }: { app: App[], projectId: string }) {
|
||||
export default function AppTable({
|
||||
app,
|
||||
projectId,
|
||||
session
|
||||
}: {
|
||||
app: App[],
|
||||
projectId: string,
|
||||
session: UserSession
|
||||
}) {
|
||||
|
||||
const { openConfirmDialog: openDialog } = useConfirmDialog();
|
||||
|
||||
@@ -54,18 +63,19 @@ export default function AppTable({ app, projectId }: { app: App[], projectId: st
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuSeparator />
|
||||
<EditAppDialog projectId={projectId} existingItem={item}>
|
||||
<DropdownMenuItem>
|
||||
<Edit2 /> <span>Edit App Name</span>
|
||||
</DropdownMenuItem>
|
||||
</EditAppDialog>
|
||||
<DropdownMenuItem className="text-red-500"
|
||||
{RoleUtils.sessionCanCreateNewAppsForProject(session, projectId) &&
|
||||
<EditAppDialog projectId={projectId} existingItem={item}>
|
||||
<DropdownMenuItem>
|
||||
<Edit2 /> <span>Edit App Name</span>
|
||||
</DropdownMenuItem>
|
||||
</EditAppDialog>}
|
||||
{RoleUtils.sessionCanDeleteAppsForProject(session, projectId) && <DropdownMenuItem className="text-red-500"
|
||||
onClick={() => openDialog({
|
||||
title: "Delete App",
|
||||
description: "Are you sure you want to delete this app? All data will be lost and this action cannot be undone.",
|
||||
}).then((result) => result ? Toast.fromAction(() => deleteApp(item.id)) : undefined)}>
|
||||
<Trash /> <span >Delete App</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuItem>}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
@@ -33,9 +33,10 @@ export default async function AppsPage({
|
||||
<PageTitle
|
||||
title="Apps"
|
||||
subtitle={`All Apps for Project "${project.name}"`}>
|
||||
<CreateProjectActions projectId={projectId} />
|
||||
{RoleUtils.sessionCanCreateNewAppsForProject(session, params.projectId) &&
|
||||
<CreateProjectActions projectId={projectId} />}
|
||||
</PageTitle>
|
||||
<AppTable app={relevantApps} projectId={project.id} />
|
||||
<AppTable session={session} app={relevantApps} projectId={project.id} />
|
||||
<ProjectBreadcrumbs project={project} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ export default async function ProjectPage() {
|
||||
const session = await getAuthUserSession();
|
||||
const data = await projectService.getAllProjects();
|
||||
const relevantProjectsForUser = data.filter((project) =>
|
||||
RoleUtils.sessionHasReadAccessToProject(session, project));
|
||||
RoleUtils.sessionHasReadAccessToProject(session, project.id));
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 pt-6">
|
||||
|
||||
@@ -35,9 +35,11 @@ type UiProjectPermission = {
|
||||
deleteApps: boolean;
|
||||
writeApps: boolean;
|
||||
readApps: boolean;
|
||||
setPermissionsPerApp: boolean;
|
||||
roleAppPermissions: {
|
||||
appId: string;
|
||||
permission: RolePermissionEnum;
|
||||
appName: string;
|
||||
permission?: RolePermissionEnum;
|
||||
}[];
|
||||
};
|
||||
|
||||
@@ -61,19 +63,25 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
saveRole(state, {
|
||||
...payload,
|
||||
id: role?.id,
|
||||
/* roleProjectPermissions: projects.map((project) => ({
|
||||
projectId: project.id,
|
||||
createApps: appPermissions.some((perm) => perm.appId === project.id && perm.readwrite),
|
||||
deleteApps: appPermissions.some((perm) => perm.appId === project.id && perm.readwrite),
|
||||
writeApps: appPermissions.some((perm) => perm.appId === project.id && perm.readwrite),
|
||||
readApps: appPermissions.some((perm) => perm.appId === project.id && (perm.read || perm.readwrite)),
|
||||
roleAppPermissions: appPermissions
|
||||
.filter((perm) => perm.appId === project.id)
|
||||
.map((perm) => ({
|
||||
appId: perm.appId,
|
||||
permission: perm.readwrite ? RolePermissionEnum.READWRITE : (perm.read ? RolePermissionEnum.READ : '')
|
||||
}))
|
||||
}))*/
|
||||
roleProjectPermissions: projects.map((project) => {
|
||||
const projectPermission = projectPermissions.find((perm) => perm.projectId === project.id);
|
||||
if (!projectPermission) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
projectId: project.id,
|
||||
createApps: projectPermission.createApps,
|
||||
deleteApps: projectPermission.deleteApps,
|
||||
writeApps: projectPermission.writeApps,
|
||||
readApps: projectPermission.readApps,
|
||||
roleAppPermissions: projectPermission.roleAppPermissions.filter(ap => !!ap.permission).map((appPerm) => {
|
||||
return {
|
||||
appId: appPerm.appId,
|
||||
permission: appPerm.permission!,
|
||||
};
|
||||
}),
|
||||
}
|
||||
}).filter((perm) => perm !== undefined),
|
||||
}), FormUtils.getInitialFormState<typeof roleEditZodModel>());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -91,13 +99,20 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
// Initialize app permissions based on role data
|
||||
const initialPermissions = projects.map(project => {
|
||||
const existingPermission = role.roleProjectPermissions?.find(p => p.projectId === project.id);
|
||||
const roleAppPermissions = project.apps.map(app => ({
|
||||
appId: app.id,
|
||||
appName: app.name,
|
||||
permission: existingPermission?.roleAppPermissions.find(appPerm => appPerm.appId === app.id)?.permission
|
||||
}));
|
||||
const hasNoAppRolePermissionsSet = roleAppPermissions.every(appPerm => !appPerm.permission);
|
||||
return {
|
||||
projectId: project.id,
|
||||
createApps: existingPermission?.createApps || false,
|
||||
deleteApps: existingPermission?.deleteApps || false,
|
||||
writeApps: existingPermission?.writeApps || false,
|
||||
readApps: existingPermission?.readApps || false,
|
||||
roleAppPermissions: existingPermission?.roleAppPermissions || []
|
||||
setPermissionsPerApp: (existingPermission?.roleAppPermissions.length ?? 0) > 0 || false,
|
||||
roleAppPermissions: hasNoAppRolePermissionsSet ? [] : roleAppPermissions
|
||||
} as UiProjectPermission;
|
||||
});
|
||||
setProjectPermissions(initialPermissions);
|
||||
@@ -109,22 +124,17 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
deleteApps: false,
|
||||
writeApps: false,
|
||||
readApps: false,
|
||||
setPermissionsPerApp: false,
|
||||
roleAppPermissions: []
|
||||
} as UiProjectPermission));
|
||||
setProjectPermissions(initialPermissions);
|
||||
|
||||
}
|
||||
}, [role, projects]);
|
||||
}, [role, projects, isOpen]);
|
||||
|
||||
|
||||
const handleReadChange = (projectId: string, checked: boolean) => {
|
||||
setProjectPermissions(prev => prev.map(perm => {
|
||||
if (perm.projectId === projectId) {
|
||||
// If read is being turned off, also turn off readwrite
|
||||
if (!checked) {
|
||||
return { ...perm, readApps: false, readwrite: false };
|
||||
}
|
||||
// If read is being turned on, just update read
|
||||
return { ...perm, readApps: checked };
|
||||
}
|
||||
return perm;
|
||||
@@ -134,12 +144,7 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
const handleReadWriteChange = (projectId: string, checked: boolean) => {
|
||||
setProjectPermissions(prev => prev.map(perm => {
|
||||
if (perm.projectId === projectId) {
|
||||
// If readwrite is being turned on, turn off read
|
||||
if (checked) {
|
||||
return { ...perm, readApps: true, writeApps: true } as UiProjectPermission;
|
||||
}
|
||||
// If readwrite is being turned off, just update read
|
||||
return { ...perm, readApps: perm.readApps, writeApps: checked } as UiProjectPermission;
|
||||
return { ...perm, writeApps: checked, readApps: checked ? true : perm.writeApps };
|
||||
}
|
||||
return perm;
|
||||
}));
|
||||
@@ -148,7 +153,7 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
const handleCreateChange = (projectId: string, checked: boolean) => {
|
||||
setProjectPermissions(prev => prev.map(perm => {
|
||||
if (perm.projectId === projectId) {
|
||||
return { ...perm, createApps: checked };
|
||||
return { ...perm, createApps: checked, readApps: checked ? true : perm.createApps };
|
||||
}
|
||||
return perm;
|
||||
}));
|
||||
@@ -157,19 +162,73 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
const handleDeleteChange = (projectId: string, checked: boolean) => {
|
||||
setProjectPermissions(prev => prev.map(perm => {
|
||||
if (perm.projectId === projectId) {
|
||||
return { ...perm, deleteApps: checked };
|
||||
return { ...perm, deleteApps: checked, readApps: checked ? true : perm.deleteApps };
|
||||
}
|
||||
return perm;
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSetPermissionsPerAppChange = (projectId: string, checked: boolean) => {
|
||||
setProjectPermissions(prev => prev.map(perm => {
|
||||
if (perm.projectId === projectId) {
|
||||
const appPermissions = checked ? projects.find(p => p.id === projectId)?.apps.map(app => ({
|
||||
appId: app.id,
|
||||
appName: app.name,
|
||||
permission: undefined
|
||||
})) || [] : [];
|
||||
return {
|
||||
...perm,
|
||||
setPermissionsPerApp: checked,
|
||||
roleAppPermissions: appPermissions,
|
||||
createApps: false,
|
||||
deleteApps: false,
|
||||
writeApps: false,
|
||||
readApps: false
|
||||
};
|
||||
}
|
||||
return perm;
|
||||
}));
|
||||
};
|
||||
|
||||
const handleAppReadChange = (appId: string, checked: boolean) =>
|
||||
setProjectPermissions(prev => prev.map(perm => {
|
||||
if (perm.roleAppPermissions.some(appPerm => appPerm.appId === appId)) {
|
||||
return {
|
||||
...perm,
|
||||
roleAppPermissions: perm.roleAppPermissions.map(appPerm => {
|
||||
if (appPerm.appId === appId) {
|
||||
return { ...appPerm, permission: checked ? RolePermissionEnum.READ : undefined };
|
||||
}
|
||||
return appPerm;
|
||||
})
|
||||
};
|
||||
}
|
||||
return perm;
|
||||
}));
|
||||
|
||||
const handleAppReadWriteChange = (appId: string, checked: boolean) =>
|
||||
setProjectPermissions(prev => prev.map(perm => {
|
||||
if (perm.roleAppPermissions.some(appPerm => appPerm.appId === appId)) {
|
||||
return {
|
||||
...perm,
|
||||
roleAppPermissions: perm.roleAppPermissions.map(appPerm => {
|
||||
if (appPerm.appId === appId) {
|
||||
return { ...appPerm, permission: checked ? RolePermissionEnum.READWRITE : undefined };
|
||||
}
|
||||
return appPerm;
|
||||
})
|
||||
};
|
||||
}
|
||||
return perm;
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div onClick={() => setIsOpen(true)}>
|
||||
{children}
|
||||
</div>
|
||||
<Dialog open={!!isOpen} onOpenChange={(isOpened) => setIsOpen(isOpened)}>
|
||||
<DialogContent className="sm:max-w-[700px]">
|
||||
<DialogContent className="sm:max-w-[900px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{role?.id ? 'Edit' : 'Create'} Role</DialogTitle>
|
||||
</DialogHeader>
|
||||
@@ -223,6 +282,7 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Project</TableHead>
|
||||
<TableHead>Individual Permissions</TableHead>
|
||||
<TableHead>Read Apps</TableHead>
|
||||
<TableHead>Write Apps</TableHead>
|
||||
<TableHead>Create Apps</TableHead>
|
||||
@@ -233,37 +293,82 @@ export default function RoleEditOverlay({ children, role, projects }: {
|
||||
{projects.map((project) => {
|
||||
const permission = projectPermissions.find(p => p.projectId === project.id);
|
||||
return (
|
||||
<TableRow key={project.id}>
|
||||
<TableCell>{project.name}</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
id={`read-${project.id}`}
|
||||
checked={permission?.readApps || false}
|
||||
onCheckedChange={(checked) => handleReadChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
id={`write-${project.id}`}
|
||||
checked={permission?.writeApps || false}
|
||||
onCheckedChange={(checked) => handleReadWriteChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
id={`create-${project.id}`}
|
||||
checked={permission?.createApps || false}
|
||||
onCheckedChange={(checked) => handleCreateChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
id={`delete-${project.id}`}
|
||||
checked={permission?.deleteApps || false}
|
||||
onCheckedChange={(checked) => handleDeleteChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<>
|
||||
<TableRow key={project.id} className={(permission?.roleAppPermissions.length ?? 0) === 0 ? 'border-b-gray-400' : ''} >
|
||||
<TableCell className="font-semibold">{project.name}</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
id={`delete-${project.id}`}
|
||||
checked={permission?.setPermissionsPerApp || false}
|
||||
onCheckedChange={(checked) => handleSetPermissionsPerAppChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
{permission?.setPermissionsPerApp ?
|
||||
<TableHead>App</TableHead>
|
||||
: <TableCell>
|
||||
<Checkbox
|
||||
id={`read-${project.id}`}
|
||||
disabled={permission?.writeApps || permission?.deleteApps || permission?.createApps}
|
||||
checked={permission?.readApps || false}
|
||||
onCheckedChange={(checked) => handleReadChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>}
|
||||
<TableCell>
|
||||
{!permission?.setPermissionsPerApp &&
|
||||
<Checkbox
|
||||
id={`write-${project.id}`}
|
||||
checked={permission?.writeApps || false}
|
||||
onCheckedChange={(checked) => handleReadWriteChange(project.id, !!checked)}
|
||||
/>}
|
||||
</TableCell>
|
||||
{permission?.setPermissionsPerApp ?
|
||||
<TableHead>Read</TableHead>
|
||||
: <TableCell>
|
||||
<Checkbox
|
||||
id={`create-${project.id}`}
|
||||
checked={permission?.createApps || false}
|
||||
onCheckedChange={(checked) => handleCreateChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>}
|
||||
{permission?.setPermissionsPerApp ?
|
||||
<TableHead>Read & Write</TableHead>
|
||||
: <TableCell>
|
||||
<Checkbox
|
||||
id={`delete-${project.id}`}
|
||||
checked={permission?.deleteApps || false}
|
||||
onCheckedChange={(checked) => handleDeleteChange(project.id, !!checked)}
|
||||
/>
|
||||
</TableCell>}
|
||||
</TableRow>
|
||||
|
||||
|
||||
{(permission?.roleAppPermissions.length ?? 0) > 0 &&
|
||||
<>
|
||||
{permission?.roleAppPermissions.map((roleAppPermission, index) =>
|
||||
|
||||
<TableRow key={roleAppPermission.appId} className={permission.roleAppPermissions.length - 1 === index ? 'border-b-gray-400' : ''}>
|
||||
<TableCell></TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell colSpan={2}>{roleAppPermission.appName}</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
id={`app-read-${roleAppPermission.appId}`}
|
||||
checked={roleAppPermission.permission === RolePermissionEnum.READ}
|
||||
onCheckedChange={(checked) => handleAppReadChange(roleAppPermission.appId, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
id={`app-readwrite-${roleAppPermission.appId}`}
|
||||
checked={roleAppPermission.permission === RolePermissionEnum.READWRITE}
|
||||
onCheckedChange={(checked) => handleAppReadWriteChange(roleAppPermission.appId, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
)}
|
||||
</>}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function AppSidebar() {
|
||||
const projects = await projectService.getAllProjects();
|
||||
|
||||
const relevantProjectsForUser = projects.filter((project) =>
|
||||
RoleUtils.sessionHasReadAccessToProject(session, project));
|
||||
RoleUtils.sessionHasReadAccessToProject(session, project.id));
|
||||
for (const project of relevantProjectsForUser) {
|
||||
project.apps = project.apps.filter((app) => RoleUtils.sessionHasReadAccessForApp(session, app.id));
|
||||
}
|
||||
|
||||
@@ -159,6 +159,8 @@ class AppService {
|
||||
revalidateTag(Tags.apps(item.projectId as string));
|
||||
revalidateTag(Tags.app(item.id as string));
|
||||
revalidateTag(Tags.projects());
|
||||
revalidateTag(Tags.roles());
|
||||
revalidateTag(Tags.users());
|
||||
}
|
||||
return savedItem;
|
||||
}
|
||||
|
||||
@@ -2,18 +2,24 @@ import dataAccess from "../../adapter/db.client";
|
||||
import bcrypt from "bcrypt";
|
||||
import { randomBytes } from "crypto";
|
||||
import quickStackService from "../qs.service";
|
||||
import { adminRoleName } from "../../../shared/model/role-extended.model.ts";
|
||||
|
||||
class PasswordChangeService {
|
||||
|
||||
async changeAdminPasswordAndPrintNewPassword() {
|
||||
const firstCreatedUser = await dataAccess.client.user.findFirst({
|
||||
where: {
|
||||
role: {
|
||||
name: adminRoleName
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
if (!firstCreatedUser) {
|
||||
console.error("No users found. QuickStack is not configured yet. Open your browser to setup quickstack");
|
||||
console.error("No admin users found. QuickStack is not configured yet. Open your browser to setup quickstack");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,7 +42,7 @@ class PasswordChangeService {
|
||||
console.log('******* Password change *******');
|
||||
console.log('*******************************');
|
||||
console.log(``);
|
||||
console.log(`New password for user ${firstCreatedUser.email} is: ${generatedPassword}`);
|
||||
console.log(`New password for admin user ${firstCreatedUser.email} is: ${generatedPassword}`);
|
||||
console.log(``);
|
||||
console.log('*******************************');
|
||||
console.log('*******************************');
|
||||
|
||||
@@ -13,6 +13,10 @@ export class RoleUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (projectPermission.roleAppPermissions.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return projectPermission.readApps;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user