Skip to content

Commit 6ab7198

Browse files
committed
User table polishing, user delete modal
1 parent 61a9290 commit 6ab7198

File tree

14 files changed

+197
-43
lines changed

14 files changed

+197
-43
lines changed

backend/models/token.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { global as logger } from "../logger.js";
1313
const ALGO = "RS256";
1414

1515
export default () => {
16-
let token_data = {};
16+
let tokenData = {};
1717

1818
const self = {
1919
/**
@@ -37,7 +37,7 @@ export default () => {
3737
if (err) {
3838
reject(err);
3939
} else {
40-
token_data = payload;
40+
tokenData = payload;
4141
resolve({
4242
token: token,
4343
payload: payload,
@@ -72,18 +72,18 @@ export default () => {
7272
reject(err);
7373
}
7474
} else {
75-
token_data = result;
75+
tokenData = result;
7676

7777
// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
7878
// For 30 days at least, we need to replace 'all' with user.
7979
if (
80-
typeof token_data.scope !== "undefined" &&
81-
_.indexOf(token_data.scope, "all") !== -1
80+
typeof tokenData.scope !== "undefined" &&
81+
_.indexOf(tokenData.scope, "all") !== -1
8282
) {
83-
token_data.scope = ["user"];
83+
tokenData.scope = ["user"];
8484
}
8585

86-
resolve(token_data);
86+
resolve(tokenData);
8787
}
8888
},
8989
);
@@ -100,15 +100,15 @@ export default () => {
100100
* @param {String} scope
101101
* @returns {Boolean}
102102
*/
103-
hasScope: (scope) => typeof token_data.scope !== "undefined" && _.indexOf(token_data.scope, scope) !== -1,
103+
hasScope: (scope) => typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, scope) !== -1,
104104

105105
/**
106106
* @param {String} key
107107
* @return {*}
108108
*/
109109
get: (key) => {
110-
if (typeof token_data[key] !== "undefined") {
111-
return token_data[key];
110+
if (typeof tokenData[key] !== "undefined") {
111+
return tokenData[key];
112112
}
113113

114114
return null;
@@ -119,7 +119,7 @@ export default () => {
119119
* @param {*} value
120120
*/
121121
set: (key, value) => {
122-
token_data[key] = value;
122+
tokenData[key] = value;
123123
},
124124

125125
/**

frontend/src/components/SiteHeader.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ export function SiteHeader() {
6666
<div className="d-none d-xl-block ps-2">
6767
<div>{currentUser?.nickname}</div>
6868
<div className="mt-1 small text-secondary">
69-
{intl.formatMessage({ id: isAdmin ? "administrator" : "standard-user" })}
69+
{intl.formatMessage({
70+
id: isAdmin ? "role.admin" : "role.standard-user",
71+
})}
7072
</div>
7173
</div>
7274
</a>

frontend/src/components/Table/Formatter/DomainsFormatter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export function DomainsFormatter({ domains, createdOn }: Props) {
1010
<div className="flex-fill">
1111
<div className="font-weight-medium">
1212
{domains.map((domain: string) => (
13-
<span key={domain} className="badge badge-lg domain-name">
13+
<a key={domain} href={`http://${domain}`} className="badge bg-yellow-lt domain-name">
1414
{domain}
15-
</span>
15+
</a>
1616
))}
1717
</div>
1818
{createdOn ? (
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
interface Props {
2+
email: string;
3+
}
4+
export function EmailFormatter({ email }: Props) {
5+
return (
6+
<a href={`mailto:${email}`} className="badge bg-yellow-lt">
7+
{email}
8+
</a>
9+
);
10+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { intl } from "src/locale";
2+
3+
interface Props {
4+
roles: string[];
5+
}
6+
export function RolesFormatter({ roles }: Props) {
7+
const r = roles || [];
8+
if (r.length === 0) {
9+
r[0] = "standard-user";
10+
}
11+
return (
12+
<>
13+
{r.map((role: string) => (
14+
<span key={role} className="badge bg-yellow-lt me-1">
15+
{intl.formatMessage({ id: `role.${role}` })}
16+
</span>
17+
))}
18+
</>
19+
);
20+
}

frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ import { intl } from "src/locale";
44
interface Props {
55
value: string;
66
createdOn?: string;
7+
disabled?: boolean;
78
}
8-
export function ValueWithDateFormatter({ value, createdOn }: Props) {
9+
export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
910
return (
1011
<div className="flex-fill">
1112
<div className="font-weight-medium">
12-
<div className="font-weight-medium">{value}</div>
13+
<div className={`font-weight-medium ${disabled ? "text-red" : ""}`}>{value}</div>
1314
</div>
1415
{createdOn ? (
15-
<div className="text-secondary mt-1">
16-
{intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })}
16+
<div className={`text-secondary mt-1 ${disabled ? "text-red" : ""}`}>
17+
{disabled
18+
? intl.formatMessage({ id: "disabled" })
19+
: intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })}
1720
</div>
1821
) : null}
1922
</div>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export * from "./CertificateFormatter";
22
export * from "./DomainsFormatter";
3+
export * from "./EmailFormatter";
34
export * from "./GravatarFormatter";
5+
export * from "./RolesFormatter";
46
export * from "./StatusFormatter";
57
export * from "./ValueWithDateFormatter";

frontend/src/locale/lang/en.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
"action.edit": "Edit",
1313
"action.enable": "Enable",
1414
"action.permissions": "Permissions",
15-
"administrator": "Administrator",
1615
"auditlog.title": "Audit Log",
1716
"cancel": "Cancel",
1817
"certificates.title": "SSL Certificates",
@@ -37,6 +36,7 @@
3736
"dead-hosts.count": "{count} 404 Hosts",
3837
"dead-hosts.empty": "There are no 404 Hosts",
3938
"dead-hosts.title": "404 Hosts",
39+
"disabled": "Disabled",
4040
"email-address": "Email address",
4141
"empty-subtitle": "Why don't you create one?",
4242
"error.invalid-auth": "Invalid email or password",
@@ -53,6 +53,7 @@
5353
"notfound.title": "Oops… You just found an error page",
5454
"notification.error": "Error",
5555
"notification.success": "Success",
56+
"notification.user-deleted": "User has been deleted",
5657
"notification.user-saved": "User has been saved",
5758
"offline": "Offline",
5859
"online": "Online",
@@ -67,10 +68,11 @@
6768
"redirection-hosts.count": "{count} Redirection Hosts",
6869
"redirection-hosts.empty": "There are no Redirection Hosts",
6970
"redirection-hosts.title": "Redirection Hosts",
71+
"role.admin": "Administrator",
72+
"role.standard-user": "Standard User",
7073
"save": "Save",
7174
"settings.title": "Settings",
7275
"sign-in": "Sign in",
73-
"standard-user": "Apache Helicopter",
7476
"streams.actions-title": "Stream #{id}",
7577
"streams.add": "Add Stream",
7678
"streams.count": "{count} Streams",
@@ -81,8 +83,11 @@
8183
"user.change-password": "Change Password",
8284
"user.confirm-password": "Confirm Password",
8385
"user.current-password": "Current Password",
86+
"user.delete.content": "Are you sure you want to delete this user?",
87+
"user.delete.title": "Delete User",
8488
"user.edit": "Edit User",
8589
"user.edit-profile": "Edit Profile",
90+
"user.flags.title": "Properties",
8691
"user.full-name": "Full Name",
8792
"user.logout": "Logout",
8893
"user.new": "New User",

frontend/src/locale/src/en.json

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@
3838
"action.permissions": {
3939
"defaultMessage": "Permissions"
4040
},
41-
"administrator": {
42-
"defaultMessage": "Administrator"
43-
},
4441
"auditlog.title": {
4542
"defaultMessage": "Audit Log"
4643
},
@@ -113,6 +110,9 @@
113110
"dead-hosts.title": {
114111
"defaultMessage": "404 Hosts"
115112
},
113+
"disabled": {
114+
"defaultMessage": "Disabled"
115+
},
116116
"email-address": {
117117
"defaultMessage": "Email address"
118118
},
@@ -158,6 +158,9 @@
158158
"notification.error": {
159159
"defaultMessage": "Error"
160160
},
161+
"notification.user-deleted": {
162+
"defaultMessage": "User has been deleted"
163+
},
161164
"notification.user-saved": {
162165
"defaultMessage": "User has been saved"
163166
},
@@ -203,6 +206,12 @@
203206
"redirection-hosts.title": {
204207
"defaultMessage": "Redirection Hosts"
205208
},
209+
"role.admin": {
210+
"defaultMessage": "Administrator"
211+
},
212+
"role.standard-user": {
213+
"defaultMessage": "Standard User"
214+
},
206215
"save": {
207216
"defaultMessage": "Save"
208217
},
@@ -212,9 +221,6 @@
212221
"sign-in": {
213222
"defaultMessage": "Sign in"
214223
},
215-
"standard-user": {
216-
"defaultMessage": "Apache Helicopter"
217-
},
218224
"streams.actions-title": {
219225
"defaultMessage": "Stream #{id}"
220226
},
@@ -245,12 +251,21 @@
245251
"user.current-password": {
246252
"defaultMessage": "Current Password"
247253
},
254+
"user.delete.title": {
255+
"defaultMessage": "Delete User"
256+
},
257+
"user.delete.content": {
258+
"defaultMessage": "Are you sure you want to delete this user?"
259+
},
248260
"user.edit": {
249261
"defaultMessage": "Edit User"
250262
},
251263
"user.edit-profile": {
252264
"defaultMessage": "Edit Profile"
253265
},
266+
"user.flags.title": {
267+
"defaultMessage": "Properties"
268+
},
254269
"user.full-name": {
255270
"defaultMessage": "Full Name"
256271
},
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { useQueryClient } from "@tanstack/react-query";
2+
import { type ReactNode, useState } from "react";
3+
import { Alert } from "react-bootstrap";
4+
import Modal from "react-bootstrap/Modal";
5+
import { Button } from "src/components";
6+
import { intl } from "src/locale";
7+
8+
interface Props {
9+
title: string;
10+
children: ReactNode;
11+
onConfirm: () => Promise<void> | void;
12+
onClose: () => void;
13+
invalidations?: any[];
14+
}
15+
export function DeleteConfirmModal({ title, children, onConfirm, onClose, invalidations }: Props) {
16+
const queryClient = useQueryClient();
17+
const [error, setError] = useState<string | null>(null);
18+
const [submitting, setSubmitting] = useState(false);
19+
20+
const onSubmit = async () => {
21+
setSubmitting(true);
22+
setError(null);
23+
try {
24+
await onConfirm();
25+
onClose();
26+
// invalidate caches as requested
27+
invalidations?.forEach((inv) => {
28+
queryClient.invalidateQueries({ queryKey: inv });
29+
});
30+
} catch (err: any) {
31+
setError(intl.formatMessage({ id: err.message }));
32+
}
33+
setSubmitting(false);
34+
};
35+
36+
return (
37+
<Modal show onHide={onClose} animation={false}>
38+
<Modal.Header closeButton>
39+
<Modal.Title>{title}</Modal.Title>
40+
</Modal.Header>
41+
<Modal.Body>
42+
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
43+
{error}
44+
</Alert>
45+
{children}
46+
</Modal.Body>
47+
<Modal.Footer>
48+
<Button data-bs-dismiss="modal" onClick={onClose} disabled={submitting}>
49+
{intl.formatMessage({ id: "cancel" })}
50+
</Button>
51+
<Button
52+
type="submit"
53+
actionType="primary"
54+
className="ms-auto btn-red"
55+
data-bs-dismiss="modal"
56+
isLoading={submitting}
57+
disabled={submitting}
58+
onClick={onSubmit}
59+
>
60+
{intl.formatMessage({ id: "action.delete" })}
61+
</Button>
62+
</Modal.Footer>
63+
</Modal>
64+
);
65+
}

0 commit comments

Comments
 (0)