Skip to content

Commit 0de26f2

Browse files
committed
Certificates react work
- renewal and download - table columns rendering - searching - deleting
1 parent 7b5c70e commit 0de26f2

File tree

16 files changed

+381
-86
lines changed

16 files changed

+381
-86
lines changed

frontend/src/api/backend/base.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,16 @@ export async function get(args: GetArgs, abortController?: AbortController) {
8080
return processResponse(await baseGet(args, abortController));
8181
}
8282

83-
export async function download(args: GetArgs, abortController?: AbortController) {
84-
return (await baseGet(args, abortController)).text();
83+
export async function download({ url, params }: GetArgs, filename = "download.file") {
84+
const headers = buildAuthHeader();
85+
const res = await fetch(buildUrl({ url, params }), { headers });
86+
const bl = await res.blob();
87+
const u = window.URL.createObjectURL(bl);
88+
const a = document.createElement("a");
89+
a.href = u;
90+
a.download = filename;
91+
a.click();
92+
window.URL.revokeObjectURL(url);
8593
}
8694

8795
interface PostArgs {
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as api from "./base";
2-
import type { Binary } from "./responseTypes";
32

4-
export async function downloadCertificate(id: number): Promise<Binary> {
5-
return await api.get({
6-
url: `/nginx/certificates/${id}/download`,
7-
});
3+
export async function downloadCertificate(id: number): Promise<void> {
4+
await api.download(
5+
{
6+
url: `/nginx/certificates/${id}/download`,
7+
},
8+
`certificate-${id}.zip`,
9+
);
810
}

frontend/src/api/backend/responseTypes.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,3 @@ export interface ValidatedCertificateResponse {
1515
certificate: Record<string, any>;
1616
certificateKey: boolean;
1717
}
18-
19-
export type Binary = number & { readonly __brand: unique symbol };
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import OverlayTrigger from "react-bootstrap/OverlayTrigger";
2+
import Popover from "react-bootstrap/Popover";
3+
import type { DeadHost, ProxyHost, RedirectionHost } from "src/api/backend";
4+
import { T } from "src/locale";
5+
6+
const getSection = (title: string, items: ProxyHost[] | RedirectionHost[] | DeadHost[]) => {
7+
if (items.length === 0) {
8+
return null;
9+
}
10+
return (
11+
<>
12+
<div>
13+
<strong>
14+
<T id={title} />
15+
</strong>
16+
</div>
17+
{items.map((host) => (
18+
<div key={host.id} className="ms-1">
19+
{host.domainNames.join(", ")}
20+
</div>
21+
))}
22+
</>
23+
);
24+
};
25+
26+
interface Props {
27+
proxyHosts: ProxyHost[];
28+
redirectionHosts: RedirectionHost[];
29+
deadHosts: DeadHost[];
30+
}
31+
export function CertificateInUseFormatter({ proxyHosts, redirectionHosts, deadHosts }: Props) {
32+
const totalCount = proxyHosts?.length + redirectionHosts?.length + deadHosts?.length;
33+
if (totalCount === 0) {
34+
return (
35+
<span className="badge bg-red-lt">
36+
<T id="certificate.not-in-use" />
37+
</span>
38+
);
39+
}
40+
41+
proxyHosts.sort();
42+
redirectionHosts.sort();
43+
deadHosts.sort();
44+
45+
const popover = (
46+
<Popover id="popover-basic">
47+
<Popover.Body>
48+
{getSection("proxy-hosts", proxyHosts)}
49+
{getSection("redirection-hosts", redirectionHosts)}
50+
{getSection("dead-hosts", deadHosts)}
51+
</Popover.Body>
52+
</Popover>
53+
);
54+
55+
return (
56+
<OverlayTrigger trigger="hover" placement="bottom" overlay={popover}>
57+
<span className="badge bg-lime-lt">
58+
<T id="certificate.in-use" />
59+
</span>
60+
</OverlayTrigger>
61+
);
62+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import cn from "classnames";
2+
import { isPast, parseISO } from "date-fns";
3+
import { DateTimeFormat } from "src/locale";
4+
5+
interface Props {
6+
value: string;
7+
highlightPast?: boolean;
8+
}
9+
export function DateFormatter({ value, highlightPast }: Props) {
10+
const dateIsPast = isPast(parseISO(value));
11+
const cl = cn({
12+
"text-danger": highlightPast && dateIsPast,
13+
});
14+
return <span className={cl}>{DateTimeFormat(value)}</span>;
15+
}

frontend/src/components/Table/Formatter/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export * from "./AccessListformatter";
22
export * from "./CertificateFormatter";
3+
export * from "./CertificateInUseFormatter";
4+
export * from "./DateFormatter";
35
export * from "./DomainsFormatter";
46
export * from "./EmailFormatter";
57
export * from "./EnabledFormatter";

frontend/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from "./useAccessList";
22
export * from "./useAccessLists";
33
export * from "./useAuditLog";
44
export * from "./useAuditLogs";
5+
export * from "./useCertificate";
56
export * from "./useCertificates";
67
export * from "./useDeadHost";
78
export * from "./useDeadHosts";
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { type Certificate, getCertificate } from "src/api/backend";
3+
4+
const fetchCertificate = (id: number) => {
5+
return getCertificate(id, ["owner"]);
6+
};
7+
8+
const useCertificate = (id: number, options = {}) => {
9+
return useQuery<Certificate, Error>({
10+
queryKey: ["certificate", id],
11+
queryFn: () => fetchCertificate(id),
12+
staleTime: 60 * 1000, // 1 minute
13+
...options,
14+
});
15+
};
16+
17+
export { useCertificate };

frontend/src/locale/lang/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@
1515
"action.close": "Close",
1616
"action.delete": "Delete",
1717
"action.disable": "Disable",
18+
"action.download": "Download",
1819
"action.edit": "Edit",
1920
"action.enable": "Enable",
2021
"action.permissions": "Permissions",
22+
"action.renew": "Renew",
2123
"action.view-details": "View Details",
2224
"auditlogs": "Audit Logs",
2325
"cancel": "Cancel",
2426
"certificate": "Certificate",
27+
"certificate.in-use": "In Use",
2528
"certificate.none.subtitle": "No certificate assigned",
2629
"certificate.none.subtitle.for-http": "This host will not use HTTPS",
2730
"certificate.none.title": "None",
31+
"certificate.not-in-use": "Not Used",
2832
"certificates": "Certificates",
2933
"certificates.custom": "Custom Certificate",
3034
"certificates.dns.credentials": "Credentials File Content",
@@ -121,6 +125,7 @@
121125
"notification.object-deleted": "{object} has been deleted",
122126
"notification.object-disabled": "{object} has been disabled",
123127
"notification.object-enabled": "{object} has been enabled",
128+
"notification.object-renewed": "{object} has been renewed",
124129
"notification.object-saved": "{object} has been saved",
125130
"notification.success": "Success",
126131
"object.actions-title": "{object} #{id}",

frontend/src/locale/src/en.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
"action.disable": {
4848
"defaultMessage": "Disable"
4949
},
50+
"action.download": {
51+
"defaultMessage": "Download"
52+
},
5053
"action.edit": {
5154
"defaultMessage": "Edit"
5255
},
@@ -56,6 +59,9 @@
5659
"action.permissions": {
5760
"defaultMessage": "Permissions"
5861
},
62+
"action.renew": {
63+
"defaultMessage": "Renew"
64+
},
5965
"action.view-details": {
6066
"defaultMessage": "View Details"
6167
},
@@ -68,6 +74,9 @@
6874
"certificate": {
6975
"defaultMessage": "Certificate"
7076
},
77+
"certificate.in-use": {
78+
"defaultMessage": "In Use"
79+
},
7180
"certificate.none.subtitle": {
7281
"defaultMessage": "No certificate assigned"
7382
},
@@ -77,6 +86,9 @@
7786
"certificate.none.title": {
7887
"defaultMessage": "None"
7988
},
89+
"certificate.not-in-use": {
90+
"defaultMessage": "Not Used"
91+
},
8092
"certificates": {
8193
"defaultMessage": "Certificates"
8294
},
@@ -365,6 +377,9 @@
365377
"notification.object-enabled": {
366378
"defaultMessage": "{object} has been enabled"
367379
},
380+
"notification.object-renewed": {
381+
"defaultMessage": "{object} has been renewed"
382+
},
368383
"notification.object-saved": {
369384
"defaultMessage": "{object} has been saved"
370385
},

0 commit comments

Comments
 (0)