Skip to content

Commit 5f01234

Browse files
committed
[DOP-27890] Add pages for managing personal tokens
1 parent 692161c commit 5f01234

19 files changed

+694
-16
lines changed

src/App.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ import { RunRaList, RunRaShow } from "@/components/run";
2828
import { OperationRaShow } from "@/components/operation";
2929
import { PlayArrow, Public, Settings } from "@mui/icons-material";
3030
import { DatasetIcon } from "@/components/icons";
31+
import {
32+
PersonalTokenRaCreate,
33+
PersonalTokenRaEdit,
34+
PersonalTokenRaList,
35+
PersonalTokenRaShow,
36+
} from "./components/personal_token";
3137

3238
const store = localStorageStore(undefined, "DataRentgen");
3339

@@ -97,6 +103,13 @@ const App = () => {
97103
show={RunRaShow}
98104
/>
99105
<Resource name="operations" show={OperationRaShow} />
106+
<Resource
107+
name="personalTokens"
108+
list={PersonalTokenRaList}
109+
show={PersonalTokenRaShow}
110+
create={PersonalTokenRaCreate}
111+
edit={PersonalTokenRaEdit}
112+
/>
100113
</Admin>
101114
</StoreContextProvider>
102115
</BrowserRouter>

src/components/dataset/DatasetRaTag.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const DatasetRaTag = () => {
1818
size="small"
1919
variant="outlined"
2020
sx={{ fontSize: "0.7rem" }}
21-
></Chip>
21+
/>
2222
</Box>
2323
))}
2424
</Stack>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { ReactElement } from "react";
2+
import {
3+
Create,
4+
DateInput,
5+
ListButton,
6+
maxLength,
7+
minValue,
8+
required,
9+
SimpleForm,
10+
TextInput,
11+
TopToolbar,
12+
useNotify,
13+
useRedirect,
14+
} from "react-admin";
15+
import PersonalTokenRaScopesCheckbox from "./PersonalTokenRaScopesCheckbox";
16+
import { PersonalTokenCreateDetailedResponseV1 } from "@/dataProvider/types";
17+
18+
const PersonalTokenRaCreateActions = () => {
19+
return (
20+
<TopToolbar>
21+
<ListButton />
22+
</TopToolbar>
23+
);
24+
};
25+
26+
const PersonalTokenRaCreate = (): ReactElement => {
27+
const notify = useNotify();
28+
const redirect = useRedirect();
29+
30+
const onSuccess = (data: PersonalTokenCreateDetailedResponseV1) => {
31+
navigator.clipboard.writeText(data.content);
32+
notify("resources.personalTokens.notification.tokenCreated", {
33+
messageArgs: { smart_count: 1 },
34+
type: "warning",
35+
autoHideDuration: null,
36+
undoable: false,
37+
});
38+
redirect("show", "personalTokens", data.id, data);
39+
};
40+
41+
const today = new Date();
42+
today.setHours(0);
43+
today.setMinutes(0);
44+
today.setSeconds(0);
45+
today.setMilliseconds(0);
46+
const todayString = today.toISOString().slice(0, 10);
47+
48+
const transform = (payload: {
49+
data: { name: string; until?: string };
50+
}) => ({
51+
name: payload.data.name,
52+
until: payload.data.until,
53+
// Other fields cannot be set
54+
});
55+
56+
return (
57+
<Create
58+
resource="personalTokens"
59+
transform={transform}
60+
mutationMode="pessimistic"
61+
mutationOptions={{ onSuccess }}
62+
actions={<PersonalTokenRaCreateActions />}
63+
>
64+
<SimpleForm sanitizeEmptyValues>
65+
<TextInput
66+
source="data.name"
67+
label="resources.personalTokens.fields.name"
68+
helperText="resources.personalTokens.helperText.name"
69+
validate={[required(), maxLength(64)]}
70+
/>
71+
<PersonalTokenRaScopesCheckbox source="data.scopes" disabled />
72+
<DateInput
73+
source="data.since"
74+
label="resources.personalTokens.fields.since"
75+
helperText="resources.personalTokens.helperText.since"
76+
defaultValue={todayString}
77+
disabled
78+
/>
79+
<DateInput
80+
source="data.until"
81+
label="resources.personalTokens.fields.until"
82+
helperText="resources.personalTokens.helperText.until"
83+
validate={minValue(todayString)}
84+
/>
85+
</SimpleForm>
86+
</Create>
87+
);
88+
};
89+
90+
export default PersonalTokenRaCreate;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { DeleteButton } from "react-admin";
2+
3+
const PersonalTokenRaDeleteButton = () => {
4+
return (
5+
<DeleteButton
6+
redirect="list"
7+
label="resources.personalTokens.actions.revoke"
8+
/>
9+
);
10+
};
11+
export default PersonalTokenRaDeleteButton;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { ReactElement } from "react";
2+
import {
3+
DateInput,
4+
DeleteButton,
5+
Edit,
6+
ListButton,
7+
minValue,
8+
SaveButton,
9+
SimpleForm,
10+
TextInput,
11+
Toolbar,
12+
useNotify,
13+
useRedirect,
14+
} from "react-admin";
15+
import PersonalTokenRaScopesCheckbox from "./PersonalTokenRaScopesCheckbox";
16+
import { PersonalTokenCreateDetailedResponseV1 } from "@/dataProvider/types";
17+
18+
const PersonalTokenRaEditToolbar = () => {
19+
return (
20+
<Toolbar>
21+
<SaveButton label="resources.personalTokens.actions.refresh" />
22+
<DeleteButton
23+
redirect="list"
24+
label="resources.personalTokens.actions.revoke"
25+
/>
26+
</Toolbar>
27+
);
28+
};
29+
30+
const PersonalTokenRaEdit = (): ReactElement => {
31+
const notify = useNotify();
32+
const redirect = useRedirect();
33+
34+
const onSuccess = (data: PersonalTokenCreateDetailedResponseV1) => {
35+
navigator.clipboard.writeText(data.content);
36+
notify("resources.personalTokens.notification.tokenCreated", {
37+
messageArgs: { smart_count: 1 },
38+
type: "warning",
39+
autoHideDuration: null,
40+
undoable: false,
41+
});
42+
redirect("show", "personalTokens", data.id, data);
43+
};
44+
45+
const today = new Date();
46+
today.setHours(0);
47+
today.setMinutes(0);
48+
today.setSeconds(0);
49+
today.setMilliseconds(0);
50+
const todayString = today.toISOString().slice(0, 10);
51+
52+
const transform = (payload: { data: { until?: string } }) => ({
53+
until: payload.data.until,
54+
// Other fields are not updatable, exclude from the payload
55+
});
56+
57+
return (
58+
<Edit
59+
resource="personalTokens"
60+
mutationMode="pessimistic"
61+
mutationOptions={{ onSuccess }}
62+
transform={transform}
63+
>
64+
<SimpleForm
65+
sanitizeEmptyValues
66+
toolbar={<PersonalTokenRaEditToolbar />}
67+
>
68+
<TextInput
69+
source="data.id"
70+
label="resources.personalTokens.fields.id"
71+
disabled
72+
/>
73+
<TextInput
74+
source="data.name"
75+
label="resources.personalTokens.fields.name"
76+
helperText="resources.personalTokens.helperText.name"
77+
disabled
78+
/>
79+
<PersonalTokenRaScopesCheckbox source="data.scopes" disabled />
80+
<DateInput
81+
source="data.since"
82+
label="resources.personalTokens.fields.since"
83+
helperText="resources.personalTokens.helperText.since"
84+
disabled
85+
/>
86+
<DateInput
87+
source="data.until"
88+
label="resources.personalTokens.fields.until"
89+
helperText="resources.personalTokens.helperText.until"
90+
validate={minValue(todayString)}
91+
/>
92+
</SimpleForm>
93+
</Edit>
94+
);
95+
};
96+
97+
export default PersonalTokenRaEdit;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { EditButton } from "react-admin";
2+
import RefreshIcon from "@mui/icons-material/Refresh";
3+
4+
const PersonalTokenRaEditButton = () => {
5+
return (
6+
<EditButton
7+
label="resources.personalTokens.actions.refresh"
8+
startIcon={<RefreshIcon />}
9+
/>
10+
);
11+
};
12+
export default PersonalTokenRaEditButton;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { ReactElement } from "react";
2+
import {
3+
List,
4+
DateField,
5+
TopToolbar,
6+
SelectColumnsButton,
7+
CreateButton,
8+
DataTable,
9+
} from "react-admin";
10+
import PersonalTokenRaScopesArray from "./PersonalTokenRaScopesArray";
11+
import PersonalTokenRaEditButton from "./PersonalTokenRaEditButton";
12+
import PersonalTokenRaDeleteButton from "./PersonalTokenRaDeleteButton";
13+
import PersonalTokenRaStatus from "./PersonalTokenRaStatus";
14+
15+
const PersonalTokenRaEditActions = () => {
16+
return (
17+
<>
18+
<TopToolbar>
19+
<CreateButton />
20+
<SelectColumnsButton />
21+
</TopToolbar>
22+
</>
23+
);
24+
};
25+
26+
const PersonalTokenRaList = (): ReactElement => {
27+
return (
28+
<List
29+
actions={<PersonalTokenRaEditActions />}
30+
resource="personalTokens"
31+
storeKey={false}
32+
>
33+
<DataTable bulkActionButtons={false}>
34+
<DataTable.Col
35+
label="resources.personalTokens.fields.id"
36+
source="data.id"
37+
disableSort
38+
/>
39+
<DataTable.Col
40+
label="resources.personalTokens.fields.name"
41+
source="data.name"
42+
disableSort
43+
/>
44+
<DataTable.Col
45+
label="resources.personalTokens.fields.scopes"
46+
source="data.scopes"
47+
field={PersonalTokenRaScopesArray}
48+
disableSort
49+
/>
50+
<DataTable.Col
51+
label="resources.personalTokens.fields.since"
52+
source="data.since"
53+
field={DateField}
54+
disableSort
55+
/>
56+
<DataTable.Col
57+
label="resources.personalTokens.fields.until"
58+
source="data.until"
59+
field={DateField}
60+
disableSort
61+
/>
62+
<DataTable.Col
63+
label="resources.personalTokens.fields.status"
64+
source="data.status"
65+
field={PersonalTokenRaStatus}
66+
disableSort
67+
/>
68+
<DataTable.Col disableSort>
69+
<PersonalTokenRaEditButton />
70+
<PersonalTokenRaDeleteButton />
71+
</DataTable.Col>
72+
</DataTable>
73+
</List>
74+
);
75+
};
76+
77+
export default PersonalTokenRaList;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { FieldProps, useRecordContext, useTranslate } from "react-admin";
2+
import { PersonalTokenDetailedResponseV1 } from "@/dataProvider/types";
3+
import { Stack, Chip, Box } from "@mui/material";
4+
5+
const PersonalTokenRaScopesArray = (props: FieldProps) => {
6+
const translate = useTranslate();
7+
const record: PersonalTokenDetailedResponseV1 | undefined =
8+
useRecordContext();
9+
10+
if (!record) {
11+
return null;
12+
}
13+
return (
14+
<Stack spacing={1}>
15+
{record.data.scopes.map((scope, index) => (
16+
<Box key={index} sx={{ width: "fit-content" }}>
17+
<Chip
18+
label={translate(
19+
`resources.personalTokens.scopes.${scope}`,
20+
)}
21+
size="small"
22+
variant="outlined"
23+
sx={{ fontSize: "0.7rem" }}
24+
/>
25+
</Box>
26+
))}
27+
</Stack>
28+
);
29+
};
30+
31+
export default PersonalTokenRaScopesArray;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { CheckboxGroupInput, InputProps } from "react-admin";
2+
3+
const PersonalTokenRaScopesCheckbox = ({
4+
source,
5+
label,
6+
...props
7+
}: InputProps) => {
8+
return (
9+
<CheckboxGroupInput
10+
source="data.scopes"
11+
label="resources.personalTokens.fields.scopes"
12+
helperText="resources.personalTokens.helperText.scopes"
13+
choices={[
14+
{
15+
id: "all:read",
16+
name: "resources.personalTokens.scopes.all:read",
17+
},
18+
{
19+
id: "all:write",
20+
name: "resources.personalTokens.scopes.all:write",
21+
},
22+
]}
23+
defaultValue={["all:read", "all:write"]}
24+
row={false}
25+
{...props}
26+
/>
27+
);
28+
};
29+
export default PersonalTokenRaScopesCheckbox;

0 commit comments

Comments
 (0)