Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/admin/registration-token-dialog.tsx
5899 views
1
/*
2
* This file is part of CoCalc: Copyright © 2025 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import {
7
Alert,
8
Button as AntdButton,
9
Checkbox,
10
DatePicker,
11
Form,
12
Input,
13
InputNumber,
14
Modal,
15
Radio,
16
Space,
17
Switch,
18
} from "antd";
19
import type { RadioChangeEvent } from "antd";
20
21
import { CancelText } from "@cocalc/frontend/i18n/components";
22
import { SiteLicenseInput } from "@cocalc/frontend/site-licenses/input";
23
import {
24
CUSTOM_PRESET_KEY,
25
EPHEMERAL_PRESETS,
26
EPHEMERAL_OFF_KEY,
27
HOUR_MS,
28
msToHours,
29
type Token,
30
} from "./types";
31
32
interface RegistrationTokenDialogProps {
33
open: boolean;
34
isEdit: boolean;
35
editingToken: Token | null;
36
onCancel: () => void;
37
onSave: (values: Token) => Promise<void>;
38
onReset: () => void;
39
error?: string;
40
form: any;
41
newRandomToken: () => string;
42
saving: boolean;
43
licenseInputKey: number;
44
}
45
46
export default function RegistrationTokenDialog({
47
open,
48
isEdit,
49
editingToken,
50
onCancel,
51
onSave,
52
onReset,
53
error,
54
form,
55
newRandomToken,
56
saving,
57
licenseInputKey,
58
}: RegistrationTokenDialogProps) {
59
const onFinish = async (values) => {
60
await onSave(values);
61
};
62
63
const onRandom = () => form.setFieldsValue({ token: newRandomToken() });
64
const limitMin = editingToken != null ? (editingToken.counter ?? 0) : 0;
65
66
function renderFooter() {
67
return [
68
<AntdButton key="random" onClick={onRandom}>
69
Randomize
70
</AntdButton>,
71
<AntdButton key="reset" onClick={onReset}>
72
Reset
73
</AntdButton>,
74
<AntdButton key="cancel" onClick={onCancel}>
75
<CancelText />
76
</AntdButton>,
77
<AntdButton
78
key="save"
79
type="primary"
80
onClick={() => form.submit()}
81
loading={saving}
82
>
83
Save
84
</AntdButton>,
85
];
86
}
87
88
function renderError() {
89
if (!error) return null;
90
return (
91
<Alert type="error" showIcon style={{ marginTop: 12 }} message={error} />
92
);
93
}
94
95
function renderEphemeralControls() {
96
return (
97
<Form.Item label="Ephemeral lifetime">
98
<Form.Item
99
noStyle
100
shouldUpdate={(prev, curr) =>
101
prev.ephemeral !== curr.ephemeral ||
102
prev._ephemeralMode !== curr._ephemeralMode
103
}
104
>
105
{(formInstance) => {
106
const ephemeral = formInstance.getFieldValue("ephemeral");
107
const mode = formInstance.getFieldValue("_ephemeralMode");
108
const customHours = msToHours(ephemeral);
109
110
const selection =
111
mode ??
112
(ephemeral != null ? CUSTOM_PRESET_KEY : EPHEMERAL_OFF_KEY);
113
114
const handleRadioChange = ({
115
target: { value },
116
}: RadioChangeEvent) => {
117
if (value === EPHEMERAL_OFF_KEY) {
118
formInstance.setFieldsValue({
119
ephemeral: undefined,
120
_ephemeralMode: EPHEMERAL_OFF_KEY,
121
});
122
return;
123
}
124
if (value === CUSTOM_PRESET_KEY) {
125
formInstance.setFieldsValue({
126
ephemeral: ephemeral != null ? ephemeral : HOUR_MS,
127
_ephemeralMode: CUSTOM_PRESET_KEY,
128
});
129
return;
130
}
131
const preset = EPHEMERAL_PRESETS.find(
132
(option) => option.key === value,
133
);
134
formInstance.setFieldsValue({
135
ephemeral: preset?.value,
136
_ephemeralMode: value,
137
});
138
};
139
140
const handleCustomHoursChange = (hours: number | string | null) => {
141
const numeric =
142
typeof hours === "string" ? parseFloat(hours) : hours;
143
if (typeof numeric === "number" && !isNaN(numeric)) {
144
formInstance.setFieldsValue({
145
ephemeral: numeric >= 1 ? numeric * HOUR_MS : HOUR_MS,
146
});
147
} else {
148
formInstance.setFieldsValue({ ephemeral: HOUR_MS });
149
}
150
};
151
152
return (
153
<>
154
<Radio.Group value={selection} onChange={handleRadioChange}>
155
<Radio value={EPHEMERAL_OFF_KEY}>Off</Radio>
156
{EPHEMERAL_PRESETS.map(({ key, label }) => (
157
<Radio key={key} value={key}>
158
{label}
159
</Radio>
160
))}
161
<Radio value={CUSTOM_PRESET_KEY}>Custom</Radio>
162
</Radio.Group>
163
{selection === CUSTOM_PRESET_KEY && (
164
<div style={{ marginTop: "10px" }}>
165
<InputNumber
166
min={1}
167
step={1}
168
value={customHours ?? 1}
169
onChange={handleCustomHoursChange}
170
placeholder="Enter hours"
171
/>{" "}
172
hours
173
</div>
174
)}
175
</>
176
);
177
}}
178
</Form.Item>
179
</Form.Item>
180
);
181
}
182
183
function renderRestrictions() {
184
return (
185
<Form.Item label="Restrictions">
186
<Space direction="vertical">
187
<Form.Item
188
name={["customize", "disableCollaborators"]}
189
valuePropName="checked"
190
noStyle
191
>
192
<Checkbox>Disable configuring collaborators</Checkbox>
193
</Form.Item>
194
<Form.Item
195
name={["customize", "disableAI"]}
196
valuePropName="checked"
197
noStyle
198
>
199
<Checkbox>Disable artificial intelligence</Checkbox>
200
</Form.Item>
201
<Form.Item
202
name={["customize", "disableInternet"]}
203
valuePropName="checked"
204
noStyle
205
>
206
<Checkbox>Disable internet access</Checkbox>
207
</Form.Item>
208
</Space>
209
</Form.Item>
210
);
211
}
212
213
function renderLicense() {
214
return (
215
<Form.Item
216
name={["customize", "license"]}
217
label="License"
218
extra="Optional: Apply a site license to projects created via this token"
219
>
220
<SiteLicenseInput
221
key={licenseInputKey}
222
defaultLicenseId={form.getFieldValue(["customize", "license"])}
223
onChange={(licenseId) =>
224
form.setFieldValue(["customize", "license"], licenseId)
225
}
226
/>
227
</Form.Item>
228
);
229
}
230
231
function renderForm() {
232
return (
233
<Form
234
form={form}
235
labelCol={{ span: 6 }}
236
wrapperCol={{ span: 18 }}
237
size="middle"
238
onFinish={onFinish}
239
>
240
<Form.Item name="token" label="Token" rules={[{ required: true }]}>
241
<Input disabled={true} />
242
</Form.Item>
243
<Form.Item
244
name="descr"
245
label="Description"
246
rules={[{ required: false }]}
247
>
248
<Input />
249
</Form.Item>
250
<Form.Item name="expires" label="Expires" rules={[{ required: false }]}>
251
<DatePicker />
252
</Form.Item>
253
<Form.Item name="limit" label="Limit" rules={[{ required: false }]}>
254
<InputNumber min={limitMin} step={1} />
255
</Form.Item>
256
<Form.Item name="ephemeral" hidden>
257
<InputNumber />
258
</Form.Item>
259
<Form.Item name="_ephemeralMode" hidden>
260
<Input />
261
</Form.Item>
262
{renderEphemeralControls()}
263
{renderRestrictions()}
264
{renderLicense()}
265
<Form.Item name="active" label="Active" valuePropName="checked">
266
<Switch />
267
</Form.Item>
268
</Form>
269
);
270
}
271
272
return (
273
<Modal
274
open={open}
275
title={isEdit ? "Edit Registration Token" : "Create Registration Token"}
276
width={800}
277
destroyOnHidden={true}
278
maskClosable={false}
279
onCancel={onCancel}
280
footer={renderFooter()}
281
>
282
{renderForm()}
283
{renderError()}
284
</Modal>
285
);
286
}
287
288