Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ms2/select role folder #362

Merged
merged 20 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dcfb5b4
fix(ms2/useParseZodErrors): avoid unnecessary rerenders
FelipeTrost Aug 14, 2024
5e645f4
refactor(ms2/FormSubmitButton): easier use
FelipeTrost Aug 14, 2024
f9f4953
fix(ms2/FormSubmitButton): use form instance for watch
FelipeTrost Aug 14, 2024
030d440
refactor(ms2/role-general-data): use zod schema and FormSubmitButton
FelipeTrost Aug 14, 2024
18cbae2
fix(ms2/abilities): identify folders for processes
FelipeTrost Aug 14, 2024
2e2705c
fix(ms2/FolderTree): correct prop types
FelipeTrost Aug 14, 2024
bf7102d
refactor(ms2/role-page): removed unnecessary component
FelipeTrost Aug 14, 2024
938c804
feat(ms2/getFolder): return root if no id
FelipeTrost Aug 14, 2024
e49d4bc
feat(ms2/FolderTree): show root as folder
FelipeTrost Aug 14, 2024
fb87827
style(ms2/FolderTree): prevent loading spinner from overflowing
FelipeTrost Aug 14, 2024
6534774
fix(ms2/FolderTree): prevent overflow
FelipeTrost Aug 14, 2024
53ea7ab
feat(ms2/role-general-data): choose role folder
FelipeTrost Aug 14, 2024
5a73080
refactor
FelipeTrost Aug 14, 2024
a522b19
revert changes to schema
FelipeTrost Aug 15, 2024
20d564b
Merge remote-tracking branch 'origin/main' into ms2/select-role-folder
FelipeTrost Nov 23, 2024
10272d4
remove accidental expression
FelipeTrost Nov 23, 2024
2c0bf54
Merge branch 'main' into ms2/select-role-folder
FelipeTrost Nov 27, 2024
03f0b86
Merge branch 'main' into ms2/select-role-folder
FelipeTrost Nov 30, 2024
ec2f638
Merge branch 'main' into ms2/select-role-folder
FelipeTrost Dec 4, 2024
cffc869
Merge branch 'main' into ms2/select-role-folder
FelipeTrost Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const DeploymentsModal = ({
});

const openFolder = async (id: string) => {
const folder = await getFolder(id);
const folder = await getFolder(environment.spaceId, id);
if ('error' in folder) {
throw new Error('Failed to fetch folder');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { getCurrentEnvironment } from '@/components/auth';
import Content from '@/components/content';
import { getRoleById } from '@/lib/data/legacy/iam/roles';
import { Result } from 'antd';
import UnauthorizedFallback from '@/components/unauthorized-fallback';
import { toCaslResource } from '@/lib/ability/caslAbility';
import RoleId from './role-id-page';
import { getMemebers } from '@/lib/data/legacy/iam/memberships';
import { getUserById } from '@/lib/data/legacy/iam/users';
import { Button, Card, Space, Tabs } from 'antd';
import { LeftOutlined } from '@ant-design/icons';
import RoleGeneralData from './roleGeneralData';
import RolePermissions from './rolePermissions';
import RoleMembers from './role-members';
import { AuthenticatedUser } from '@/lib/data/user-schema';
import SpaceLink from '@/components/space-link';
import { getFolderById } from '@/lib/data/legacy/folders';

const Page = async ({
params: { roleId, environmentId },
Expand All @@ -24,6 +29,8 @@ const Page = async ({
</Content>
);

if (!ability.can('manage', toCaslResource('Role', role))) return <UnauthorizedFallback />;

const usersInRole = role.members.map((member) =>
getUserById(member.userId),
) as AuthenticatedUser[];
Expand All @@ -34,9 +41,52 @@ const Page = async ({
.filter(({ userId }) => !roleUserSet.has(userId))
.map((user) => getUserById(user.userId)) as AuthenticatedUser[];

if (!ability.can('manage', toCaslResource('Role', role))) return <UnauthorizedFallback />;
const roleParentFolder = role.parentId ? getFolderById(role.parentId, ability) : undefined;

return <RoleId role={role} usersNotInRole={usersNotInRole} usersInRole={usersInRole} />;
return (
<Content
title={
<Space>
<SpaceLink href={`/iam/roles`}>
<Button icon={<LeftOutlined />} type="text">
Roles
</Button>
</SpaceLink>
{role?.name}
</Space>
}
>
<div style={{ maxWidth: '800px', margin: 'auto' }}>
<Card>
<Tabs
items={[
{
key: 'generalData',
label: 'General Data',
children: <RoleGeneralData role={role} roleParentFolder={roleParentFolder} />,
},
{
key: 'permissions',
label: 'Permissions',
children: <RolePermissions role={role} />,
},
{
key: 'members',
label: 'Manage Members',
children: (
<RoleMembers
role={role}
usersNotInRole={usersNotInRole}
usersInRole={usersInRole}
/>
),
},
]}
/>
</Card>
</div>
</Content>
);
};

export default Page;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,41 +1,105 @@
'use client';

import { toCaslResource } from '@/lib/ability/caslAbility';
import { Alert, App, Button, DatePicker, Form, Input } from 'antd';
import { FC, useEffect, useState } from 'react';
import { Alert, App, Button, DatePicker, Form, Input, Modal, Space } from 'antd';
import { FC, useState } from 'react';
import dayjs from 'dayjs';
import germanLocale from 'antd/es/date-picker/locale/de_DE';
import { useAbilityStore } from '@/lib/abilityStore';
import { updateRole } from '@/lib/data/roles';
import { useRouter } from 'next/navigation';
import { Role } from '@/lib/data/role-schema';
import { Role, RoleInputSchema } from '@/lib/data/role-schema';
import { useEnvironment } from '@/components/auth-can';
import useParseZodErrors, { antDesignInputProps } from '@/lib/useParseZodErrors';
import FormSubmitButton from '@/components/form-submit-button';
import { FolderTree } from '@/components/FolderTree';
import { ProcessListItemIcon } from '@/components/process-list';
import { Folder } from '@/lib/data/folder-schema';

const RoleGeneralData: FC<{ role: Role }> = ({ role: _role }) => {
const InputSchema = RoleInputSchema.omit({ environmentId: true, permissions: true });

const FolderInput = ({
onChange,
defaultFolder,
}: {
value?: string;
onChange?: (id: Role['parentId']) => void;
defaultFolder?: Folder;
}) => {
const [modalOpen, setModalOpen] = useState(false);
const [selectedFolder, setSelectedFolder] = useState<{ type: string; name: string } | undefined>(
() =>
defaultFolder && {
type: 'folder',
name: defaultFolder.parentId ? defaultFolder.name : '< root >',
},
);

return (
<>
<Modal
title="Choose a folder"
open={modalOpen}
onCancel={() => setModalOpen(false)}
closeIcon={null}
>
<Space direction="vertical" style={{ maxWidth: '100%' }}>
<Button
onClick={() => {
onChange?.(undefined);
setSelectedFolder(undefined);
setModalOpen(false);
}}
type="default"
danger
>
Clear folder
</Button>
<FolderTree
newChildrenHook={(nodes) => nodes.filter((node) => node.element.type === 'folder')}
treeProps={{
onSelect(_, info) {
const element = info.node.element;
if (element.type !== 'folder') return;

onChange?.(element.id);
setSelectedFolder(element);
setModalOpen(false);
},
}}
showRootAsFolder
/>
</Space>
</Modal>
<Button onClick={() => setModalOpen(true)}>
{selectedFolder ? (
<>
<ProcessListItemIcon item={selectedFolder as any} /> {selectedFolder.name}
</>
) : (
'Select folder'
)}
</Button>
</>
);
};

const RoleGeneralData: FC<{ role: Role; roleParentFolder?: Folder }> = ({
role: _role,
roleParentFolder,
}) => {
const { message } = App.useApp();
const ability = useAbilityStore((store) => store.ability);
const [form] = Form.useForm();
const router = useRouter();
const environment = useEnvironment();

const [submittable, setSubmittable] = useState(false);
const values = Form.useWatch('name', form);

useEffect(() => {
form.validateFields({ validateOnly: true }).then(
() => {
setSubmittable(true);
},
() => {
setSubmittable(false);
},
);
}, [form, values]);
const [errors, parseInput] = useParseZodErrors(InputSchema);

const role = toCaslResource('Role', _role);

async function submitChanges(values: Record<string, any>) {
if (typeof values.expirationDayJs === 'object') {
if (typeof values?.expirationDayJs === 'object') {
values.expiration = (values.expirationDayJs as dayjs.Dayjs).toISOString();
delete values.expirationDayJs;
}
Expand All @@ -59,19 +123,18 @@ const RoleGeneralData: FC<{ role: Role }> = ({ role: _role }) => {
</>
)}

<Form.Item
label="Name"
name="name"
rules={[{ required: true, message: 'this field is required' }]}
required
>
<Form.Item label="Name" name="name" {...antDesignInputProps(errors, 'name')} required>
<Input
placeholder="input placeholder"
disabled={!ability.can('update', role, { field: 'name' })}
/>
</Form.Item>

<Form.Item label="Description" name="description">
<Form.Item
label="Description"
name="description"
{...antDesignInputProps(errors, 'description')}
>
<Input.TextArea
placeholder="input placeholder"
disabled={!ability.can('update', role, { field: 'description' })}
Expand All @@ -88,10 +151,12 @@ const RoleGeneralData: FC<{ role: Role }> = ({ role: _role }) => {
/>
</Form.Item>

<Form.Item label="Folder" name="parentId">
<FolderInput defaultFolder={roleParentFolder} />
</Form.Item>

<Form.Item>
<Button type="primary" htmlType="submit" disabled={!submittable}>
Update Role
</Button>
<FormSubmitButton submitText="Update Role" isValidData={(values) => !!parseInput(values)} />
</Form.Item>
</Form>
);
Expand Down
Loading