Merge pull request #1699 from Pallavikumarimdb/make-easier-to-delete

Make it easier to delete things
This commit is contained in:
Milo Schwartz
2025-10-19 14:00:19 -04:00
committed by GitHub
17 changed files with 72 additions and 140 deletions

View File

@@ -47,9 +47,8 @@
"edit": "Edit", "edit": "Edit",
"siteConfirmDelete": "Confirm Delete Site", "siteConfirmDelete": "Confirm Delete Site",
"siteDelete": "Delete Site", "siteDelete": "Delete Site",
"siteMessageRemove": "Once removed, the site will no longer be accessible. All resources and targets associated with the site will also be removed.", "siteMessageRemove": "Once removed the site will no longer be accessible. All targets associated with the site will also be removed.",
"siteMessageConfirm": "To confirm, please type the name of the site below.", "siteQuestionRemove": "Are you sure you want to remove the site from the organization?",
"siteQuestionRemove": "Are you sure you want to remove the site {selectedSite} from the organization?",
"siteManageSites": "Manage Sites", "siteManageSites": "Manage Sites",
"siteDescription": "Allow connectivity to your network through secure tunnels", "siteDescription": "Allow connectivity to your network through secure tunnels",
"siteCreate": "Create Site", "siteCreate": "Create Site",
@@ -154,8 +153,7 @@
"protected": "Protected", "protected": "Protected",
"notProtected": "Not Protected", "notProtected": "Not Protected",
"resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.", "resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.",
"resourceMessageConfirm": "To confirm, please type the name of the resource below.", "resourceQuestionRemove": "Are you sure you want to remove the resource from the organization?",
"resourceQuestionRemove": "Are you sure you want to remove the resource {selectedResource} from the organization?",
"resourceHTTP": "HTTPS Resource", "resourceHTTP": "HTTPS Resource",
"resourceHTTPDescription": "Proxy requests to your app over HTTPS using a subdomain or base domain.", "resourceHTTPDescription": "Proxy requests to your app over HTTPS using a subdomain or base domain.",
"resourceRaw": "Raw TCP/UDP Resource", "resourceRaw": "Raw TCP/UDP Resource",
@@ -220,7 +218,7 @@
"orgDeleteConfirm": "Confirm Delete Organization", "orgDeleteConfirm": "Confirm Delete Organization",
"orgMessageRemove": "This action is irreversible and will delete all associated data.", "orgMessageRemove": "This action is irreversible and will delete all associated data.",
"orgMessageConfirm": "To confirm, please type the name of the organization below.", "orgMessageConfirm": "To confirm, please type the name of the organization below.",
"orgQuestionRemove": "Are you sure you want to remove the organization {selectedOrg}?", "orgQuestionRemove": "Are you sure you want to remove the organization?",
"orgUpdated": "Organization updated", "orgUpdated": "Organization updated",
"orgUpdatedDescription": "The organization has been updated.", "orgUpdatedDescription": "The organization has been updated.",
"orgErrorUpdate": "Failed to update organization", "orgErrorUpdate": "Failed to update organization",
@@ -287,9 +285,8 @@
"apiKeysAdd": "Generate API Key", "apiKeysAdd": "Generate API Key",
"apiKeysErrorDelete": "Error deleting API key", "apiKeysErrorDelete": "Error deleting API key",
"apiKeysErrorDeleteMessage": "Error deleting API key", "apiKeysErrorDeleteMessage": "Error deleting API key",
"apiKeysQuestionRemove": "Are you sure you want to remove the API key {selectedApiKey} from the organization?", "apiKeysQuestionRemove": "Are you sure you want to remove the API key from the organization?",
"apiKeysMessageRemove": "Once removed, the API key will no longer be able to be used.", "apiKeysMessageRemove": "Once removed, the API key will no longer be able to be used.",
"apiKeysMessageConfirm": "To confirm, please type the name of the API key below.",
"apiKeysDeleteConfirm": "Confirm Delete API Key", "apiKeysDeleteConfirm": "Confirm Delete API Key",
"apiKeysDelete": "Delete API Key", "apiKeysDelete": "Delete API Key",
"apiKeysManage": "Manage API Keys", "apiKeysManage": "Manage API Keys",
@@ -305,8 +302,7 @@
"userDeleteConfirm": "Confirm Delete User", "userDeleteConfirm": "Confirm Delete User",
"userDeleteServer": "Delete User from Server", "userDeleteServer": "Delete User from Server",
"userMessageRemove": "The user will be removed from all organizations and be completely removed from the server.", "userMessageRemove": "The user will be removed from all organizations and be completely removed from the server.",
"userMessageConfirm": "To confirm, please type the name of the user below.", "userQuestionRemove": "Are you sure you want to permanently delete user from the server?",
"userQuestionRemove": "Are you sure you want to permanently delete {selectedUser} from the server?",
"licenseKey": "License Key", "licenseKey": "License Key",
"valid": "Valid", "valid": "Valid",
"numberOfSites": "Number of Sites", "numberOfSites": "Number of Sites",
@@ -339,7 +335,7 @@
"fossorialLicense": "View Fossorial Commercial License & Subscription Terms", "fossorialLicense": "View Fossorial Commercial License & Subscription Terms",
"licenseMessageRemove": "This will remove the license key and all associated permissions granted by it.", "licenseMessageRemove": "This will remove the license key and all associated permissions granted by it.",
"licenseMessageConfirm": "To confirm, please type the license key below.", "licenseMessageConfirm": "To confirm, please type the license key below.",
"licenseQuestionRemove": "Are you sure you want to delete the license key {selectedKey} ?", "licenseQuestionRemove": "Are you sure you want to delete the license key ?",
"licenseKeyDelete": "Delete License Key", "licenseKeyDelete": "Delete License Key",
"licenseKeyDeleteConfirm": "Confirm Delete License Key", "licenseKeyDeleteConfirm": "Confirm Delete License Key",
"licenseTitle": "Manage License Status", "licenseTitle": "Manage License Status",
@@ -372,7 +368,7 @@
"inviteRemoveErrorDescription": "An error occurred while removing the invitation.", "inviteRemoveErrorDescription": "An error occurred while removing the invitation.",
"inviteRemoved": "Invitation removed", "inviteRemoved": "Invitation removed",
"inviteRemovedDescription": "The invitation for {email} has been removed.", "inviteRemovedDescription": "The invitation for {email} has been removed.",
"inviteQuestionRemove": "Are you sure you want to remove the invitation {email}?", "inviteQuestionRemove": "Are you sure you want to remove the invitation?",
"inviteMessageRemove": "Once removed, this invitation will no longer be valid. You can always re-invite the user later.", "inviteMessageRemove": "Once removed, this invitation will no longer be valid. You can always re-invite the user later.",
"inviteMessageConfirm": "To confirm, please type the email address of the invitation below.", "inviteMessageConfirm": "To confirm, please type the email address of the invitation below.",
"inviteQuestionRegenerate": "Are you sure you want to regenerate the invitation for {email}? This will revoke the previous invitation.", "inviteQuestionRegenerate": "Are you sure you want to regenerate the invitation for {email}? This will revoke the previous invitation.",
@@ -398,9 +394,8 @@
"userErrorOrgRemoveDescription": "An error occurred while removing the user.", "userErrorOrgRemoveDescription": "An error occurred while removing the user.",
"userOrgRemoved": "User removed", "userOrgRemoved": "User removed",
"userOrgRemovedDescription": "The user {email} has been removed from the organization.", "userOrgRemovedDescription": "The user {email} has been removed from the organization.",
"userQuestionOrgRemove": "Are you sure you want to remove {email} from the organization?", "userQuestionOrgRemove": "Are you sure you want to remove this user from the organization?",
"userMessageOrgRemove": "Once removed, this user will no longer have access to the organization. You can always re-invite them later, but they will need to accept the invitation again.", "userMessageOrgRemove": "Once removed, this user will no longer have access to the organization. You can always re-invite them later, but they will need to accept the invitation again.",
"userMessageOrgConfirm": "To confirm, please type the name of the of the user below.",
"userRemoveOrgConfirm": "Confirm Remove User", "userRemoveOrgConfirm": "Confirm Remove User",
"userRemoveOrg": "Remove User from Organization", "userRemoveOrg": "Remove User from Organization",
"users": "Users", "users": "Users",
@@ -742,7 +737,7 @@
"idpManageDescription": "View and manage identity providers in the system", "idpManageDescription": "View and manage identity providers in the system",
"idpDeletedDescription": "Identity provider deleted successfully", "idpDeletedDescription": "Identity provider deleted successfully",
"idpOidc": "OAuth2/OIDC", "idpOidc": "OAuth2/OIDC",
"idpQuestionRemove": "Are you sure you want to permanently delete the identity provider {name}?", "idpQuestionRemove": "Are you sure you want to permanently delete the identity provider?",
"idpMessageRemove": "This will remove the identity provider and all associated configurations. Users who authenticate through this provider will no longer be able to log in.", "idpMessageRemove": "This will remove the identity provider and all associated configurations. Users who authenticate through this provider will no longer be able to log in.",
"idpMessageConfirm": "To confirm, please type the name of the identity provider below.", "idpMessageConfirm": "To confirm, please type the name of the identity provider below.",
"idpConfirmDelete": "Confirm Delete Identity Provider", "idpConfirmDelete": "Confirm Delete Identity Provider",
@@ -1211,9 +1206,8 @@
"domainCreate": "Create Domain", "domainCreate": "Create Domain",
"domainCreatedDescription": "Domain created successfully", "domainCreatedDescription": "Domain created successfully",
"domainDeletedDescription": "Domain deleted successfully", "domainDeletedDescription": "Domain deleted successfully",
"domainQuestionRemove": "Are you sure you want to remove the domain {domain} from your account?", "domainQuestionRemove": "Are you sure you want to remove the domain from your account?",
"domainMessageRemove": "Once removed, the domain will no longer be associated with your account.", "domainMessageRemove": "Once removed, the domain will no longer be associated with your account.",
"domainMessageConfirm": "To confirm, please type the domain name below.",
"domainConfirmDelete": "Confirm Delete Domain", "domainConfirmDelete": "Confirm Delete Domain",
"domainDelete": "Delete Domain", "domainDelete": "Delete Domain",
"domain": "Domain", "domain": "Domain",
@@ -1563,9 +1557,8 @@
"searchRemoteExitNodes": "Search nodes...", "searchRemoteExitNodes": "Search nodes...",
"remoteExitNodeAdd": "Add Node", "remoteExitNodeAdd": "Add Node",
"remoteExitNodeErrorDelete": "Error deleting node", "remoteExitNodeErrorDelete": "Error deleting node",
"remoteExitNodeQuestionRemove": "Are you sure you want to remove the node {selectedNode} from the organization?", "remoteExitNodeQuestionRemove": "Are you sure you want to remove the node from the organization?",
"remoteExitNodeMessageRemove": "Once removed, the node will no longer be accessible.", "remoteExitNodeMessageRemove": "Once removed, the node will no longer be accessible.",
"remoteExitNodeMessageConfirm": "To confirm, please type the name of the node below.",
"remoteExitNodeConfirmDelete": "Confirm Delete Node", "remoteExitNodeConfirmDelete": "Confirm Delete Node",
"remoteExitNodeDelete": "Delete Node", "remoteExitNodeDelete": "Delete Node",
"sidebarRemoteExitNodes": "Remote Nodes", "sidebarRemoteExitNodes": "Remote Nodes",
@@ -1894,5 +1887,9 @@
"pathRewriteRegex": "Regex", "pathRewriteRegex": "Regex",
"pathRewriteStrip": "Strip", "pathRewriteStrip": "Strip",
"pathRewriteStripLabel": "strip", "pathRewriteStripLabel": "strip",
"sidebarEnableEnterpriseLicense": "Enable Enterprise License" "sidebarEnableEnterpriseLicense": "Enable Enterprise License",
"cannotbeUndone": "This can not be undone.",
"toConfirm": "to confirm",
"deleteClientQuestion": "Are you sure you want to remove the client from the site and organization?",
"clientMessageRemove": "Once removed, the client will no longer be able to connect to the site."
} }

View File

@@ -270,17 +270,12 @@ export default function ExitNodesTable({
setSelectedNode(null); setSelectedNode(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("remoteExitNodeQuestionRemove", { {t("remoteExitNodeQuestionRemove")}
selectedNode:
selectedNode?.name || selectedNode?.id
})}
</p> </p>
<p>{t("remoteExitNodeMessageRemove")}</p> <p>{t("remoteExitNodeMessageRemove")}</p>
<p>{t("remoteExitNodeMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("remoteExitNodeConfirmDelete")} buttonText={t("remoteExitNodeConfirmDelete")}

View File

@@ -168,13 +168,10 @@ export default function GeneralPage() {
}} }}
dialog={ dialog={
<div> <div>
<p className="mb-2"> <p>
{t("orgQuestionRemove", { {t("orgQuestionRemove")}
selectedOrg: org?.org.name
})}
</p> </p>
<p className="mb-2">{t("orgMessageRemove")}</p> <p>{t("orgMessageRemove")}</p>
<p>{t("orgMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("orgDeleteConfirm")} buttonText={t("orgDeleteConfirm")}

View File

@@ -315,18 +315,13 @@ export default function LicensePage() {
setSelectedLicenseKey(null); setSelectedLicenseKey(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("licenseQuestionRemove", { {t("licenseQuestionRemove")}
selectedKey: obfuscateLicenseKey(
selectedLicenseKey.licenseKey
)
})}
</p> </p>
<p> <p>
<b>{t("licenseMessageRemove")}</b> <b>{t("licenseMessageRemove")}</b>
</p> </p>
<p>{t("licenseMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("licenseKeyDeleteConfirm")} buttonText={t("licenseKeyDeleteConfirm")}

View File

@@ -237,21 +237,14 @@ export default function UsersTable({ users }: Props) {
setSelected(null); setSelected(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("userQuestionRemove", { {t("userQuestionRemove")}
selectedUser:
selected?.email ||
selected?.name ||
selected?.username
})}
</p> </p>
<p> <p>
<b>{t("userMessageRemove")}</b> {t("userMessageRemove")}
</p> </p>
<p>{t("userMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("userDeleteConfirm")} buttonText={t("userDeleteConfirm")}

View File

@@ -193,7 +193,7 @@ export default function IdpTable({ idps }: Props) {
setSelectedIdp(null); setSelectedIdp(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("idpQuestionRemove", { {t("idpQuestionRemove", {
name: selectedIdp.name name: selectedIdp.name

View File

@@ -256,7 +256,7 @@ export default function UsersTable({ users }: Props) {
setSelected(null); setSelected(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("userQuestionRemove", { {t("userQuestionRemove", {
selectedUser: selectedUser:

View File

@@ -177,19 +177,14 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
setSelected(null); setSelected(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("apiKeysQuestionRemove", { {t("apiKeysQuestionRemove")}
selectedApiKey:
selected?.name || selected?.id
})}
</p> </p>
<p> <p>
<b>{t("apiKeysMessageRemove")}</b> {t("apiKeysMessageRemove")}
</p> </p>
<p>{t("apiKeysMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("apiKeysDeleteConfirm")} buttonText={t("apiKeysDeleteConfirm")}

View File

@@ -277,25 +277,12 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) {
setSelectedClient(null); setSelectedClient(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
Are you sure you want to remove the client{" "} {t("deleteClientQuestion")}
<b>
{selectedClient?.name || selectedClient?.id}
</b>{" "}
from the site and organization?
</p> </p>
<p> <p>
<b> {t("clientMessageRemove")}
Once removed, the client will no longer be
able to connect to the site.{" "}
</b>
</p>
<p>
To confirm, please type the name of the client
below.
</p> </p>
</div> </div>
} }

View File

@@ -44,6 +44,7 @@ import { Description } from "@radix-ui/react-toast";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import CopyToClipboard from "./CopyToClipboard";
type InviteUserFormProps = { type InviteUserFormProps = {
open: boolean; open: boolean;
@@ -110,6 +111,17 @@ export default function InviteUserForm({
<CredenzaBody> <CredenzaBody>
<div className="mb-4 break-all overflow-hidden"> <div className="mb-4 break-all overflow-hidden">
{dialog} {dialog}
<div className="mt-2 mb-6 font-bold text-red-700">
{t("cannotbeUndone")}
</div>
<div>
<div className="flex items-center gap-2">
{t("type")}
<span className="px-2 py-1 rounded-md bg-secondary"><CopyToClipboard text={string} /></span>
{t("toConfirm")}
</div>
</div>
</div> </div>
<Form {...form}> <Form {...form}>
<form <form

View File

@@ -237,16 +237,13 @@ export default function DomainsTable({ domains }: Props) {
setSelectedDomain(null); setSelectedDomain(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("domainQuestionRemove", { {t("domainQuestionRemove")}
domain: selectedDomain.baseDomain
})}
</p> </p>
<p> <p>
<b>{t("domainMessageRemove")}</b> {t("domainMessageRemove")}
</p> </p>
<p>{t("domainMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("domainConfirmDelete")} buttonText={t("domainConfirmDelete")}

View File

@@ -175,14 +175,11 @@ export default function InvitationsTable({
setSelectedInvitation(null); setSelectedInvitation(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("inviteQuestionRemove", { {t("inviteQuestionRemove")}
email: selectedInvitation?.email || ""
})}
</p> </p>
<p>{t("inviteMessageRemove")}</p> <p>{t("inviteMessageRemove")}</p>
<p>{t("inviteMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("inviteRemoveConfirm")} buttonText={t("inviteRemoveConfirm")}

View File

@@ -185,19 +185,14 @@ export default function OrgApiKeysTable({
setSelected(null); setSelected(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("apiKeysQuestionRemove", { {t("apiKeysQuestionRemove")}
selectedApiKey:
selected?.name || selected?.id
})}
</p> </p>
<p> <p>
<b>{t("apiKeysMessageRemove")}</b> {t("apiKeysMessageRemove")}
</p> </p>
<p>{t("apiKeysMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("apiKeysDeleteConfirm")} buttonText={t("apiKeysDeleteConfirm")}

View File

@@ -704,17 +704,12 @@ export default function ResourcesTable({
}} }}
dialog={ dialog={
<div> <div>
<p className="mb-2"> <p>
{t("resourceQuestionRemove", { {t("resourceQuestionRemove")}
selectedResource: </p>
selectedResource?.name || <p>
selectedResource?.id {t("resourceMessageRemove")}
})}
</p> </p>
<p className="mb-2">{t("resourceMessageRemove")}</p>
<p>{t("resourceMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("resourceDeleteConfirm")} buttonText={t("resourceDeleteConfirm")}
@@ -733,17 +728,12 @@ export default function ResourcesTable({
}} }}
dialog={ dialog={
<div> <div>
<p className="mb-2"> <p>
{t("resourceQuestionRemove", { {t("resourceQuestionRemove")}
selectedResource: </p>
selectedInternalResource?.name || <p>
selectedInternalResource?.id {t("resourceMessageRemove")}
})}
</p> </p>
<p className="mb-2">{t("resourceMessageRemove")}</p>
<p>{t("resourceMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("resourceDeleteConfirm")} buttonText={t("resourceDeleteConfirm")}

View File

@@ -418,17 +418,11 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
setSelectedSite(null); setSelectedSite(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div className="">
<p> <p>
{t("siteQuestionRemove", { {t("siteQuestionRemove")}
selectedSite:
selectedSite?.name || selectedSite?.id
})}
</p> </p>
<p>{t("siteMessageRemove")}</p> <p>{t("siteMessageRemove")}</p>
<p>{t("siteMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("siteConfirmDelete")} buttonText={t("siteConfirmDelete")}

View File

@@ -273,20 +273,11 @@ export default function UsersTable({ users: u }: UsersTableProps) {
setSelectedUser(null); setSelectedUser(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("userQuestionOrgRemove", { {t("userQuestionOrgRemove")}
email:
selectedUser?.email ||
selectedUser?.name ||
selectedUser?.username ||
""
})}
</p> </p>
<p>{t("userMessageOrgRemove")}</p> <p>{t("userMessageOrgRemove")}</p>
<p>{t("userMessageOrgConfirm")}</p>
</div> </div>
} }
buttonText={t("userRemoveOrgConfirm")} buttonText={t("userRemoveOrgConfirm")}

View File

@@ -177,16 +177,13 @@ export default function IdpTable({ idps, orgId }: Props) {
setSelectedIdp(null); setSelectedIdp(null);
}} }}
dialog={ dialog={
<div className="space-y-4"> <div>
<p> <p>
{t("idpQuestionRemove", { {t("idpQuestionRemove")}
name: selectedIdp.name
})}
</p> </p>
<p> <p>
<b>{t("idpMessageRemove")}</b> {t("idpMessageRemove")}
</p> </p>
<p>{t("idpMessageConfirm")}</p>
</div> </div>
} }
buttonText={t("idpConfirmDelete")} buttonText={t("idpConfirmDelete")}