From da68fe29ac475aae9be6f11812bd048a3e9a93dd Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 24 Sep 2025 19:45:00 +1000 Subject: [PATCH] 404 hosts polish --- README.md | 8 ----- backend/internal/dead-host.js | 4 ++- backend/internal/nginx.js | 5 +++- backend/routes/nginx/dead_hosts.js | 4 +-- .../Table/Formatter/DomainsFormatter.tsx | 24 +++++++++++++-- .../Table/Formatter/EventFormatter.tsx | 2 ++ frontend/src/locale/lang/en.json | 8 +++++ frontend/src/locale/src/en.json | 30 +++++++++++++++++-- frontend/src/pages/Nginx/DeadHosts/Table.tsx | 18 ++++++++--- .../pages/Nginx/DeadHosts/TableWrapper.tsx | 17 +++++++++-- 10 files changed, 95 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 2116a55a..7d5743b8 100644 --- a/README.md +++ b/README.md @@ -88,14 +88,6 @@ Sometimes this can take a little bit because of the entropy of keys. [http://127.0.0.1:81](http://127.0.0.1:81) -Default Admin User: -``` -Email: admin@example.com -Password: changeme -``` - -Immediately after logging in with this default user you will be asked to modify your details and change your password. - ## Contributing diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index 0cbb0105..21b12012 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -63,7 +63,7 @@ const internalDeadHost = { action: "created", object_type: "dead-host", object_id: row.id, - meta: _.assign({}, data.meta || {}, row.meta), + meta: thisData, }); if (createCertificate) { @@ -240,6 +240,7 @@ const internalDeadHost = { // Delete Nginx Config await internalNginx.deleteConfig("dead_host", row); await internalNginx.reload(); + // Add to audit log await internalAuditLog.add(access, { action: "deleted", @@ -247,6 +248,7 @@ const internalDeadHost = { object_id: row.id, meta: _.omit(row, omissions()), }); + return true; }, /** diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index c4216fb5..4870874d 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -301,8 +301,11 @@ const internalNginx = { * @param {String} filename */ deleteFile: (filename) => { - logger.debug(`Deleting file: ${filename}`); + if (!fs.existsSync(filename)) { + return; + } try { + logger.debug(`Deleting file: ${filename}`); fs.unlinkSync(filename); } catch (err) { logger.debug("Could not delete file:", JSON.stringify(err, null, 2)); diff --git a/backend/routes/nginx/dead_hosts.js b/backend/routes/nginx/dead_hosts.js index 7fc64a07..5323c731 100644 --- a/backend/routes/nginx/dead_hosts.js +++ b/backend/routes/nginx/dead_hosts.js @@ -121,7 +121,7 @@ router /** * PUT /api/nginx/dead-hosts/123 * - * Update and existing dead-host + * Update an existing dead-host */ .put(async (req, res, next) => { try { @@ -138,7 +138,7 @@ router /** * DELETE /api/nginx/dead-hosts/123 * - * Update and existing dead-host + * Delete a dead-host */ .delete(async (req, res, next) => { try { diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx index 6f5a037f..ca7735f1 100644 --- a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -4,14 +4,32 @@ interface Props { domains: string[]; createdOn?: string; } + +const DomainLink = ({ domain }: { domain: string }) => { + // when domain contains a wildcard, make the link go nowhere. + let onClick: ((e: React.MouseEvent) => void) | undefined; + if (domain.includes("*")) { + onClick = (e: React.MouseEvent) => e.preventDefault(); + } + return ( + + {domain} + + ); +}; + export function DomainsFormatter({ domains, createdOn }: Props) { return (
{domains.map((domain: string) => ( - - {domain} - + ))}
{createdOn ? ( diff --git a/frontend/src/components/Table/Formatter/EventFormatter.tsx b/frontend/src/components/Table/Formatter/EventFormatter.tsx index 77892b3d..b248dbfb 100644 --- a/frontend/src/components/Table/Formatter/EventFormatter.tsx +++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx @@ -10,6 +10,8 @@ const getEventValue = (event: AuditLog) => { switch (event.objectType) { case "user": return event.meta?.name; + case "dead-host": + return event.meta?.domainNames?.join(", ") || "N/A"; default: return `UNKNOWN EVENT TYPE: ${event.objectType}`; } diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index 0844b59b..6e785f08 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -41,6 +41,8 @@ "column.status": "Status", "created-on": "Created: {date}", "dashboard.title": "Dashboard", + "dead-host.delete.content": "Are you sure you want to delete this 404 host?", + "dead-host.delete.title": "Delete 404 Host", "dead-host.edit": "Edit 404 Host", "dead-host.new": "New 404 Host", "dead-hosts.actions-title": "404 Host #{id}", @@ -67,8 +69,11 @@ "error.max-domains": "Too many domains, max is {max}", "error.passwords-must-match": "Passwords must match", "error.required": "This is required", + "event.created-dead-host": "Created 404 Host", "event.created-user": "Created User", "event.deleted-user": "Deleted User", + "event.disabled-dead-host": "Disabled 404 Host", + "event.enabled-dead-host": "Enabled 404 Host", "event.updated-user": "Updated User", "footer.github-fork": "Fork me on Github", "hosts.title": "Hosts", @@ -84,6 +89,9 @@ "notfound.title": "Oops… You just found an error page", "notification.dead-host-saved": "404 Host has been saved", "notification.error": "Error", + "notification.host-deleted": "Host has been deleted", + "notification.host-disabled": "Host has been disabled", + "notification.host-enabled": "Host has been enabled", "notification.success": "Success", "notification.user-deleted": "User has been deleted", "notification.user-saved": "User has been saved", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index a8f21e64..be7f2a17 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -134,12 +134,18 @@ "dead-hosts.count": { "defaultMessage": "{count} 404 Hosts" }, - "dead-host.edit": { - "defaultMessage": "Edit 404 Host" - }, "dead-hosts.empty": { "defaultMessage": "There are no 404 Hosts" }, + "dead-host.delete.content": { + "defaultMessage": "Are you sure you want to delete this 404 host?" + }, + "dead-host.delete.title": { + "defaultMessage": "Delete 404 Host" + }, + "dead-host.edit": { + "defaultMessage": "Edit 404 Host" + }, "dead-host.new": { "defaultMessage": "New 404 Host" }, @@ -200,12 +206,21 @@ "error.required": { "defaultMessage": "This is required" }, + "event.created-dead-host": { + "defaultMessage": "Created 404 Host" + }, "event.created-user": { "defaultMessage": "Created User" }, "event.deleted-user": { "defaultMessage": "Deleted User" }, + "event.disabled-dead-host": { + "defaultMessage": "Disabled 404 Host" + }, + "event.enabled-dead-host": { + "defaultMessage": "Enabled 404 Host" + }, "event.updated-user": { "defaultMessage": "Updated User" }, @@ -254,9 +269,18 @@ "notification.error": { "defaultMessage": "Error" }, + "notification.host-deleted": { + "defaultMessage": "Host has been deleted" + }, "notification.user-deleted": { "defaultMessage": "User has been deleted" }, + "notification.host-disabled": { + "defaultMessage": "Host has been disabled" + }, + "notification.host-enabled": { + "defaultMessage": "Host has been enabled" + }, "notification.user-saved": { "defaultMessage": "User has been saved" }, diff --git a/frontend/src/pages/Nginx/DeadHosts/Table.tsx b/frontend/src/pages/Nginx/DeadHosts/Table.tsx index 2ee15b34..23007a29 100644 --- a/frontend/src/pages/Nginx/DeadHosts/Table.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/Table.tsx @@ -12,9 +12,10 @@ interface Props { isFetching?: boolean; onEdit?: (id: number) => void; onDelete?: (id: number) => void; + onDisableToggle?: (id: number, enabled: boolean) => void; onNew?: () => void; } -export default function Table({ data, isFetching, onEdit, onDelete, onNew }: Props) { +export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew }: Props) { const columnHelper = createColumnHelper(); const columns = useMemo( () => [ @@ -83,9 +84,18 @@ export default function Table({ data, isFetching, onEdit, onDelete, onNew }: Pro {intl.formatMessage({ id: "action.edit" })} - + { + e.preventDefault(); + onDisableToggle?.(info.row.original.id, !info.row.original.enabled); + }} + > - {intl.formatMessage({ id: "action.disable" })} + {intl.formatMessage({ + id: info.row.original.enabled ? "action.disable" : "action.enable", + })}
({ diff --git a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx index 665483e3..f627b650 100644 --- a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx @@ -1,6 +1,8 @@ import { IconSearch } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import Alert from "react-bootstrap/Alert"; +import { deleteDeadHost, toggleDeadHost } from "src/api/backend"; import { Button, LoadingPage } from "src/components"; import { useDeadHosts } from "src/hooks"; import { intl } from "src/locale"; @@ -9,6 +11,7 @@ import { showSuccess } from "src/notifications"; import Table from "./Table"; export default function TableWrapper() { + const queryClient = useQueryClient(); const [deleteId, setDeleteId] = useState(0); const [editId, setEditId] = useState(0 as number | "new"); const { isFetching, isLoading, isError, error, data } = useDeadHosts(["owner", "certificate"]); @@ -22,10 +25,17 @@ export default function TableWrapper() { } const handleDelete = async () => { - // await deleteUser(deleteId); + await deleteDeadHost(deleteId); showSuccess(intl.formatMessage({ id: "notification.host-deleted" })); }; + const handleDisableToggle = async (id: number, enabled: boolean) => { + await toggleDeadHost(id, enabled); + queryClient.invalidateQueries({ queryKey: ["dead-hosts"] }); + queryClient.invalidateQueries({ queryKey: ["dead-host", id] }); + showSuccess(intl.formatMessage({ id: enabled ? "notification.host-enabled" : "notification.host-disabled" })); + }; + return (
@@ -60,17 +70,18 @@ export default function TableWrapper() { isFetching={isFetching} onEdit={(id: number) => setEditId(id)} onDelete={(id: number) => setDeleteId(id)} + onDisableToggle={handleDisableToggle} onNew={() => setEditId("new")} /> {editId ? setEditId(0)} /> : null} {deleteId ? ( setDeleteId(0)} invalidations={[["dead-hosts"], ["dead-host", deleteId]]} > - {intl.formatMessage({ id: "user.delete.content" })} + {intl.formatMessage({ id: "dead-host.delete.content" })} ) : null}