Skip to content

Commit e203047

Browse files
committed
feat(front): ui for managing service accounts
1 parent 9f39163 commit e203047

File tree

24 files changed

+1033
-99
lines changed

24 files changed

+1033
-99
lines changed

front/assets/js/app.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import { default as Agents} from "./agents";
6666
import { default as AddPeople } from "./people/add_people";
6767
import { default as EditPerson } from "./people/edit_person";
6868
import { default as SyncPeople } from "./people/sync_people";
69+
import { default as ServiceAccounts } from "./service_accounts";
6970
import { default as Report } from "./report";
7071

7172
import { InitializingScreen } from "./project_onboarding/initializing";
@@ -294,12 +295,23 @@ export var App = {
294295
GroupManagement.init();
295296
new Star();
296297

297-
const addPeopleAppRoot = document.getElementById("add-people");
298-
if (addPeopleAppRoot) {
299-
AddPeople({
300-
dom: addPeopleAppRoot,
301-
config: addPeopleAppRoot.dataset,
302-
});
298+
299+
// Initialize Preact apps
300+
const serviceAccountsEl = document.getElementById("service-accounts");
301+
if (serviceAccountsEl) {
302+
const config = JSON.parse(serviceAccountsEl.dataset.config);
303+
ServiceAccounts({ dom: serviceAccountsEl, config });
304+
}
305+
306+
const addPeopleEl = document.getElementById("add-people");
307+
if (addPeopleEl) {
308+
AddPeople({ dom: addPeopleEl, config: addPeopleEl.dataset });
309+
}
310+
311+
const syncPeopleEl = document.querySelector(".app-sync-people");
312+
if (syncPeopleEl) {
313+
const config = JSON.parse(syncPeopleEl.dataset.config);
314+
SyncPeople({ dom: syncPeopleEl, config });
303315
}
304316

305317
document.querySelectorAll(".app-edit-person").forEach((editPersonAppRoot) => {
@@ -516,6 +528,7 @@ export var App = {
516528

517529
window.Notice.init();
518530

531+
519532
$(document).on("click", ".x-select-on-click", function (event) {
520533
event.currentTarget.setSelectionRange(0, event.currentTarget.value.length);
521534
});

front/assets/js/people/add_people/index.tsx

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const App = () => {
4444
<span className="material-symbols-outlined mr2">person_add</span>
4545
{`Add people`}
4646
</button>
47-
<Modal isOpen={isOpen} close={() => close(false)} title="Add people">
47+
<Modal isOpen={isOpen} close={() => close(false)} title="Add new people" width="w-70-m">
4848
<AddNewUsers close={close}/>
4949
</Modal>
5050
</Fragment>
@@ -134,16 +134,9 @@ const AddNewUsers = (props: { close: (reload: boolean) => void, }) => {
134134
};
135135

136136
return (
137-
<div
138-
className="bg-white br3 shadow-1 w-90 w-70-m mw6 relative popup"
139-
style={{ top: `200px`, transform: `translate(-50%, 0)` }}
140-
>
141-
<div className="flex items-center justify-between ph4 pt4 mb3">
142-
<h2 className="f3 mb0">Add new people</h2>
143-
</div>
144-
137+
<div className="pa4">
145138
{userProviders.length > 1 && (
146-
<div className="mb3 button-group ph4 w-100 items-center justify-center">
139+
<div className="mb3 button-group w-100 items-center justify-center">
147140
{userProviders.map(userProviderBox)}
148141
</div>
149142
)}
@@ -158,7 +151,7 @@ const AddNewUsers = (props: { close: (reload: boolean) => void, }) => {
158151
return (
159152
<Fragment key={idx}>
160153
{loading && (
161-
<div className="ph4 pb4 tc">
154+
<div className="pb4 tc">
162155
<toolbox.Asset path="images/spinner.svg"/>
163156
</div>
164157
)}
@@ -280,7 +273,7 @@ const ProvideVia = (props: ProvideViaProps) => {
280273
return (
281274
<Fragment>
282275
<div className="ph4 pb4">{message}</div>
283-
<div className="flex justify-end items-center mt2 pb4 ph4">
276+
<div className="flex justify-end items-center mt2">
284277
<button
285278
className="btn btn-primary ml3"
286279
onClick={() => props?.onCancel(anyInvites)}
@@ -294,11 +287,11 @@ const ProvideVia = (props: ProvideViaProps) => {
294287

295288
return (
296289
<Fragment>
297-
<div className="ph4">
290+
<div className="">
298291
<label className="db mb2">Invite users to join your organization</label>
299292
</div>
300293
<div
301-
className="ph4 pv1 w-100"
294+
className="pv1 ph1 w-100"
302295
style={{ maxHeight: `400px`, overflow: `auto` }}
303296
>
304297
{!props.noManualInvite && (
@@ -370,7 +363,7 @@ const ProvideVia = (props: ProvideViaProps) => {
370363
</div>
371364

372365
{collaborators.length != 0 && (
373-
<div className="flex justify-end items-center mt2 pb4 ph4">
366+
<div className="flex justify-end items-center mt2">
374367
<a
375368
className="gray underline pointer"
376369
onClick={() => setSelectedCollaborators(collaborators)}
@@ -513,7 +506,7 @@ const ProvideViaEmail = (props: ProvideViaEmailProps) => {
513506

514507
return (
515508
<Fragment>
516-
<div className="ph4" style={{ maxHeight: `400px`, overflow: `auto` }}>
509+
<div className="ph1" style={{ maxHeight: `400px`, overflow: `auto` }}>
517510
{!arePeopleInvited && (
518511
<label className="db mb2">Email addresses and usernames</label>
519512
)}
@@ -593,7 +586,7 @@ const ProvideViaEmail = (props: ProvideViaEmailProps) => {
593586
))}
594587
</div>
595588
{arePeopleInvited && (
596-
<div className="flex justify-end pb4 ph4">
589+
<div className="flex justify-end">
597590
<button
598591
className="btn btn-primary"
599592
onClick={() => props?.onCancel(true)}
@@ -603,7 +596,7 @@ const ProvideViaEmail = (props: ProvideViaEmailProps) => {
603596
</div>
604597
)}
605598
{!arePeopleInvited && (
606-
<div className="flex justify-end pb4 ph4">
599+
<div className="flex justify-end">
607600
<button
608601
className="btn btn-secondary mr3"
609602
onClick={() => props?.onCancel(false)}

front/assets/js/people/edit_person/index.tsx

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -163,53 +163,48 @@ export const Button = () => {
163163
<span className="material-symbols-outlined mr1">manage_accounts</span>
164164
<span>Edit</span>
165165
</button>
166-
<toolbox.Modal isOpen={isOpen} close={close} title="Add people">
167-
<div className="bg-white br3 shadow-1 w-90 w-50-m mw6 relative popup">
168-
<div className="pa3">
169-
<div className="flex items-center justify-between mb3">
170-
<h2 className="f3 mb0">Edit user</h2>
171-
</div>
172-
<div className="mb3">
173-
<label className="db mb2 b">Email address</label>
174-
<div className="flex">
175-
<input
176-
type="email"
177-
className="form-control w-100"
178-
value={email}
179-
onChange={onEmailChanged}
180-
disabled={emailResponse.status == `loading`}
181-
/>
182-
</div>
183-
<ResponseHandler response={emailResponse}/>
166+
<toolbox.Modal isOpen={isOpen} close={close} title="Edit user">
167+
<div className="pa3">
168+
<div className="mb3">
169+
<label className="db mb2 b">Email address</label>
170+
<div className="flex">
171+
<input
172+
type="email"
173+
className="form-control w-100"
174+
value={email}
175+
onChange={onEmailChanged}
176+
disabled={emailResponse.status == `loading`}
177+
/>
184178
</div>
179+
<ResponseHandler response={emailResponse}/>
180+
</div>
185181

186-
<div className="mb3">
187-
<label className="db mb2 b">Password</label>
188-
<PasswordReset user={user}/>
189-
</div>
182+
<div className="mb3">
183+
<label className="db mb2 b">Password</label>
184+
<PasswordReset user={user}/>
185+
</div>
190186

191-
<div>
192-
<label className="db mb2 b">Role</label>
193-
<ChangeRole
194-
user={user}
195-
selectRole={setSelectedRole}
196-
selectedRole={selectedRole}
197-
/>
198-
<ResponseHandler response={roleResponse}/>
199-
</div>
200-
<div className="flex justify-end mt4">
201-
<button className="btn btn-secondary mr3" onClick={close}>
187+
<div>
188+
<label className="db mb2 b">Role</label>
189+
<ChangeRole
190+
user={user}
191+
selectRole={setSelectedRole}
192+
selectedRole={selectedRole}
193+
/>
194+
<ResponseHandler response={roleResponse}/>
195+
</div>
196+
<div className="flex justify-end mt4">
197+
<button className="btn btn-secondary mr3" onClick={close}>
202198
Cancel
203-
</button>
199+
</button>
204200

205-
<button
206-
className="btn btn-primary"
207-
onClick={save}
208-
disabled={!roleChanged && !emailChanged}
209-
>
201+
<button
202+
className="btn btn-primary"
203+
onClick={save}
204+
disabled={!roleChanged && !emailChanged}
205+
>
210206
Save changes
211-
</button>
212-
</div>
207+
</button>
213208
</div>
214209
</div>
215210
</toolbox.Modal>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useState, useContext } from "preact/hooks";
2+
import { Modal } from "js/toolbox";
3+
import { ConfigContext } from "../config";
4+
import { ServiceAccountsAPI } from "../utils/api";
5+
import { TokenDisplay } from "./TokenDisplay";
6+
import * as toolbox from "js/toolbox";
7+
8+
interface CreateServiceAccountProps {
9+
isOpen: boolean;
10+
onClose: () => void;
11+
onCreated: () => void;
12+
}
13+
14+
export const CreateServiceAccount = ({ isOpen, onClose, onCreated }: CreateServiceAccountProps) => {
15+
const config = useContext(ConfigContext);
16+
const api = new ServiceAccountsAPI(config);
17+
18+
const [name, setName] = useState(``);
19+
const [description, setDescription] = useState(``);
20+
const [loading, setLoading] = useState(false);
21+
const [error, setError] = useState<string | null>(null);
22+
const [token, setToken] = useState<string | null>(null);
23+
24+
const handleSubmit = async (e: Event) => {
25+
e.preventDefault();
26+
setError(null);
27+
setLoading(true);
28+
29+
const response = await api.create(name, description);
30+
31+
if (response.error) {
32+
setError(response.error);
33+
setLoading(false);
34+
} else if (response.data) {
35+
setToken(response.data.api_token);
36+
setLoading(false);
37+
}
38+
};
39+
40+
const handleClose = () => {
41+
if (token) {
42+
onCreated();
43+
}
44+
setName(``);
45+
setDescription(``);
46+
setError(null);
47+
setToken(null);
48+
onClose();
49+
};
50+
51+
const canSubmit = name.trim().length > 0 && !loading;
52+
53+
return (
54+
<Modal isOpen={isOpen} close={handleClose} title="Create Service Account">
55+
{!token ? (
56+
<form onSubmit={(e) => void handleSubmit(e)}>
57+
<div className="pa3">
58+
<div className="mb3">
59+
<label className="db mb2 f6 b">Name *</label>
60+
<input
61+
type="text"
62+
className="form-control w-100"
63+
value={name}
64+
onInput={(e) => setName(e.currentTarget.value)}
65+
placeholder="e.g., CI/CD Pipeline"
66+
disabled={loading}
67+
autoFocus
68+
/>
69+
</div>
70+
71+
<div className="mb3">
72+
<label className="db mb2 f6 b">Description</label>
73+
<textarea
74+
className="form-control w-100"
75+
value={description}
76+
onInput={(e) => setDescription(e.currentTarget.value)}
77+
placeholder="Optional description of what this service account is used for"
78+
rows={3}
79+
disabled={loading}
80+
/>
81+
</div>
82+
83+
{error && (
84+
<div className="bg-washed-red ba b--red br2 pa2 mb3">
85+
<p className="f6 mb0 red">{error}</p>
86+
</div>
87+
)}
88+
</div>
89+
90+
<div className="flex justify-end items-center pa2 bt b--black-10">
91+
<button
92+
type="button"
93+
className="btn btn-secondary mr3"
94+
onClick={handleClose}
95+
disabled={loading}
96+
>
97+
Cancel
98+
</button>
99+
<button
100+
type="submit"
101+
className="btn btn-primary"
102+
disabled={!canSubmit}
103+
>
104+
{loading ? (
105+
<span className="flex items-center">
106+
<toolbox.Asset path="images/spinner.svg" className="w1 h1 mr2"/>
107+
Creating...
108+
</span>
109+
) : (
110+
`Create Service Account`
111+
)}
112+
</button>
113+
</div>
114+
</form>
115+
) : (
116+
<TokenDisplay token={token} onClose={handleClose}/>
117+
)}
118+
</Modal>
119+
);
120+
};

0 commit comments

Comments
 (0)