From add9b8dfb07e9392271883058eee9e31f255eb50 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 4 Jun 2026 22:25:03 -0700 Subject: [PATCH] many minor visual improvements --- messages/en-US.json | 8 +- .../public/ProxyResourceTargetsForm.tsx | 17 +- .../resources/public/[niceId]/ssh/page.tsx | 29 +- .../settings/resources/public/create/page.tsx | 99 +- .../settings/sites/[niceId]/general/page.tsx | 4 +- src/app/admin/idp/create/page.tsx | 4 +- src/app/globals.css | 4 +- src/components/BrowserGatewayTargetForm.tsx | 2 - src/components/Credenza.tsx | 2 +- src/components/HealthCheckCredenza.tsx | 1431 +++++++++-------- src/components/PrivateResourceForm.tsx | 117 +- src/components/ProxyResourcesTable.tsx | 2 +- src/components/SetResourceHeaderAuthForm.tsx | 2 +- src/components/Settings.tsx | 36 + src/components/SwitchInput.tsx | 4 +- src/components/labels-selector.tsx | 37 +- src/components/newt-install-commands.tsx | 48 +- .../CreatePolicyAuthMethodsSectionForm.tsx | 17 +- .../EditPolicyAuthMethodsSectionForm.tsx | 2 +- .../ResourcePolicySubForms.tsx | 2 +- 20 files changed, 998 insertions(+), 869 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index b7d670789..6690a126d 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -101,6 +101,8 @@ "sitesTableViewPrivateResources": "View Private Resources", "siteInstallNewt": "Install Site", "siteInstallNewtDescription": "Install the site connector for your system", + "siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see docs.pangolin.net/manage/sites/install-kubernetes.", + "siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see docs.pangolin.net/manage/sites/install-advantech.", "WgConfiguration": "WireGuard Configuration", "WgConfigurationDescription": "Use the following configuration to connect to the network", "operatingSystem": "Operating System", @@ -1220,8 +1222,10 @@ "addLabels": "Add labels", "siteLabelsTab": "Labels", "siteLabelsDescription": "Manage labels associated with this site.", - "labelsNotFound": "Labels not found", + "labelsNotFound": "No labels found.", + "labelsEmptyCreateHint": "Start typing above to create a label.", "labelSearch": "Search labels", + "labelSearchOrCreate": "Search or create a label", "accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}", "labelOverflowCount": "+{count, plural, one {# label} other {# labels}}", "accessLabelFilterClear": "Clear label filters", @@ -2073,7 +2077,7 @@ "sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.", "sshDaemonPort": "Daemon Port", "sshServerDestination": "Server Destination", - "sshServerDestinationDescription": "Configure the destination and port of the SSH server", + "sshServerDestinationDescription": "Configure the destination of the SSH server", "destination": "Destination", "bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.", "roleAllowSsh": "Allow SSH", diff --git a/src/app/[orgId]/settings/resources/public/ProxyResourceTargetsForm.tsx b/src/app/[orgId]/settings/resources/public/ProxyResourceTargetsForm.tsx index 5863a50a8..7289c2767 100644 --- a/src/app/[orgId]/settings/resources/public/ProxyResourceTargetsForm.tsx +++ b/src/app/[orgId]/settings/resources/public/ProxyResourceTargetsForm.tsx @@ -334,19 +334,15 @@ export function ProxyResourceTargetsForm({ {row.original.siteType === "newt" ? ( ) : ( - @@ -535,7 +531,7 @@ export function ProxyResourceTargetsForm({ accessorKey: "enabled", header: () => {t("enabled")}, cell: ({ row }) => ( -
+
@@ -554,9 +550,8 @@ export function ProxyResourceTargetsForm({ const actionsColumn: ColumnDef = { id: "actions", - header: () => {t("actions")}, cell: ({ row }) => ( -
+
@@ -494,7 +504,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) { )} {mode === "autoSave" && ( -
+
- {/* Strategy picker */} - ( - - - ( + + + + handleChange( + "hcMode", + value, + field.onChange ) - }, - { - id: "tcp", - title: "TCP", - description: t( - "healthCheckStrategyTcp" - ) - }, - // lets hide these for now until they are implemented - // { - // id: "snmp", - // title: "SNMP", - // description: t( - // "healthCheckStrategySnmp" - // ) - // }, - // { - // id: "icmp", - // title: "Ping (ICMP)", - // description: t( - // "healthCheckStrategyIcmp" - // ) - // } - ]} - value={field.value} - onChange={(value) => - handleChange( - "hcMode", - value, - field.onChange - ) - } - /> - - - - )} - /> + } + /> + + + + )} + />
@@ -625,325 +641,369 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) { "pointer-events-none opacity-60" )} > - {/* Contact-sales banner for SNMP / ICMP */} - {isSnmpOrIcmp && } + {/* Contact-sales banner for SNMP / ICMP */} + {isSnmpOrIcmp && ( + + )} - {!isSnmpOrIcmp && ( - <> - {/* Scheme / Hostname / Port */} - {isTcp ? ( -
- ( - - - {t( - "healthHostname" - )} - - - + {/* Scheme / Hostname / Port */} + {isTcp ? ( +
+ ( + + + {t( + "healthHostname" + )} + + + + handleChange( + "hcHostname", + e + .target + .value, + () => + field.onChange( + e + ) + ) + } + /> + + + + )} + /> + ( + + + {t( + "healthPort" + )} + + + + handleChange( + "hcPort", + e + .target + .value, + field.onChange + ) + } + /> + + + + )} + /> +
+ ) : ( +
+ ( + + + {t( + "healthScheme" + )} + + - handleChange( - "hcPort", - e - .target - .value, + "hcScheme", + value, field.onChange ) } - /> - - - - )} - /> -
- ) : ( -
- ( - - - {t( - "healthScheme" - )} - - + + + )} + /> + ( + + + {t( + "healthHostname" + )} + - - - + + handleChange( + "hcHostname", + e + .target + .value, + () => + field.onChange( + e + ) + ) + } + /> - - - HTTP - - - HTTPS - - - - - - )} - /> - ( - - - {t( - "healthHostname" - )} - - - + + )} + /> + ( + + + {t( + "healthPort" + )} + + + + handleChange( + "hcPort", + e + .target + .value, + field.onChange + ) + } + /> + + + + )} + /> +
+ )} + + {/* Method / Path / Timeout (HTTP) */} + {!isTcp && ( +
+ ( + + + {t( + "httpMethod" + )} + + - handleChange( - "hcPort", - e - .target - .value, + "hcMethod", + value, field.onChange ) } - /> - - - - )} - /> -
- )} - - {/* Method / Path / Timeout (HTTP) */} - {!isTcp && ( -
- ( - - - {t( - "httpMethod" - )} - - - - - )} - /> - ( - - - {t( - "healthCheckPath" - )} - - - - handleChange( - "hcPath", - e - .target - .value, - () => - field.onChange( - e - ) - ) + value={ + field.value } - /> - - - - )} - /> + > + + + + + + + + GET + + + POST + + + HEAD + + + PUT + + + DELETE + + + + + + )} + /> + ( + + + {t( + "healthCheckPath" + )} + + + + handleChange( + "hcPath", + e + .target + .value, + () => + field.onChange( + e + ) + ) + } + /> + + + + )} + /> + ( + + + {t( + "timeoutSeconds" + )} + + + + handleChange( + "hcTimeout", + parseInt( + e + .target + .value + ), + field.onChange + ) + } + /> + + + + )} + /> +
+ )} + + {/* Timeout for TCP */} + {isTcp && ( )} /> -
- )} - - {/* Timeout for TCP */} - {isTcp && ( - ( - - - {t( - "timeoutSeconds" - )} - - - - handleChange( - "hcTimeout", - parseInt( - e - .target - .value - ), - field.onChange - ) - } - /> - - - - )} - /> - )} - - )} + )} + + )}
@@ -1035,323 +1057,340 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) { "pointer-events-none opacity-60" )} > - {/* Contact-sales banner for SNMP / ICMP */} - {isSnmpOrIcmp && } + {/* Contact-sales banner for SNMP / ICMP */} + {isSnmpOrIcmp && ( + + )} - {!isSnmpOrIcmp && ( - <> - {/* Healthy interval + threshold */} -
- ( - - - {t( - "healthyIntervalSeconds" - )} - - - - handleChange( - "hcInterval", - parseInt( - e - .target - .value - ), - field.onChange - ) - } - /> - - - - )} - /> - ( - - - {t( - "healthyThreshold" - )} - - - - handleChange( - "hcHealthyThreshold", - parseInt( - e - .target - .value - ), - field.onChange - ) - } - /> - - - - )} - /> -
- - {/* Unhealthy interval + threshold */} -
- ( - - - {t( - "unhealthyIntervalSeconds" - )} - - - - handleChange( - "hcUnhealthyInterval", - parseInt( - e - .target - .value - ), - field.onChange - ) - } - /> - - - - )} - /> - ( - - - {t( - "unhealthyThreshold" - )} - - - - handleChange( - "hcUnhealthyThreshold", - parseInt( - e - .target - .value - ), - field.onChange - ) - } - /> - - - - )} - /> -
- - {/* HTTP-only advanced fields */} - {!isTcp && ( - <> - {/* Expected status + TLS server name */} -
- ( - - - {t( - "expectedResponseCodes" - )} - - - { - const val = - e - .target - .value; - const value = - val - ? parseInt( - val - ) - : null; - handleChange( - "hcStatus", - value, - field.onChange - ); - }} - /> - - - - )} - /> - ( - - - {t( - "tlsServerName" - )} - - - - handleChange( - "hcTlsServerName", - e - .target - .value, - () => - field.onChange( - e - ) - ) - } - /> - - - - )} - /> -
- - {/* Follow redirects */} + {!isSnmpOrIcmp && ( + <> + {/* Healthy interval + threshold */} +
( - - - {t( - "followRedirects" - )} - - - - handleChange( - "hcFollowRedirects", - value, - field.onChange - ) - } - /> - - - )} - /> - - {/* Custom headers */} - ( {t( - "customHeaders" + "healthyIntervalSeconds" )} - handleChange( - "hcHeaders", - value, + "hcInterval", + parseInt( + e + .target + .value + ), field.onChange ) } - rows={ - 4 - } /> - - {t( - "customHeadersDescription" - )} - )} /> - - )} - - )} + ( + + + {t( + "healthyThreshold" + )} + + + + handleChange( + "hcHealthyThreshold", + parseInt( + e + .target + .value + ), + field.onChange + ) + } + /> + + + + )} + /> +
+ + {/* Unhealthy interval + threshold */} +
+ ( + + + {t( + "unhealthyIntervalSeconds" + )} + + + + handleChange( + "hcUnhealthyInterval", + parseInt( + e + .target + .value + ), + field.onChange + ) + } + /> + + + + )} + /> + ( + + + {t( + "unhealthyThreshold" + )} + + + + handleChange( + "hcUnhealthyThreshold", + parseInt( + e + .target + .value + ), + field.onChange + ) + } + /> + + + + )} + /> +
+ + {/* HTTP-only advanced fields */} + {!isTcp && ( + <> + {/* Expected status + TLS server name */} +
+ ( + + + {t( + "expectedResponseCodes" + )} + + + { + const val = + e + .target + .value; + const value = + val + ? parseInt( + val + ) + : null; + handleChange( + "hcStatus", + value, + field.onChange + ); + }} + /> + + + + )} + /> + ( + + + {t( + "tlsServerName" + )} + + + + handleChange( + "hcTlsServerName", + e + .target + .value, + () => + field.onChange( + e + ) + ) + } + /> + + + + )} + /> +
+ + {/* Follow redirects */} + ( + + + + handleChange( + "hcFollowRedirects", + value, + field.onChange + ) + } + /> + + + )} + /> + + {/* Custom headers */} + ( + + + {t( + "customHeaders" + )} + + + + handleChange( + "hcHeaders", + value, + field.onChange + ) + } + rows={ + 4 + } + /> + + + {t( + "customHeadersDescription" + )} + + + + )} + /> + + )} + + )}
diff --git a/src/components/PrivateResourceForm.tsx b/src/components/PrivateResourceForm.tsx index 4a8b0b62b..c985a2d67 100644 --- a/src/components/PrivateResourceForm.tsx +++ b/src/components/PrivateResourceForm.tsx @@ -1,5 +1,10 @@ "use client"; +import { + SettingsSubsectionDescription, + SettingsSubsectionHeader, + SettingsSubsectionTitle +} from "@app/components/Settings"; import { HorizontalTabs } from "@app/components/HorizontalTabs"; import { OptionSelect, @@ -1807,10 +1812,10 @@ export function PrivateResourceForm({ /> {/* Mode */} -
-

+

+ {t("sshServerMode")} -

+
value={sshServerMode} options={[ @@ -1864,10 +1869,10 @@ export function PrivateResourceForm({ />
-
-

+

+ {t("sshAuthenticationMethod")} -

+
-

+

+ {t("sshAuthDaemonLocation")} -

+
( - - - {t("sshDaemonPort")} - - - { - if (sshSectionDisabled) - return; - const v = - e.target.value; - if (v === "") { - field.onChange( - null - ); - return; +
+ ( + + + {t("sshDaemonPort")} + + + - - - - )} - /> + value={ + field.value ?? "" + } + onChange={(e) => { + if ( + sshSectionDisabled + ) + return; + const v = + e.target.value; + if (v === "") { + field.onChange( + null + ); + return; + } + const num = + parseInt(v, 10); + field.onChange( + Number.isNaN( + num + ) + ? null + : num + ); + }} + /> + + + + )} + /> +
)}
)} diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index 0b761a540..760b68f2f 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -823,7 +823,7 @@ function TargetStatusCell({
) : ( - t("labelsNotFound") +
+ + {t("labelsNotFound")} + + + {t("labelsEmptyCreateHint")} + +
)} diff --git a/src/components/newt-install-commands.tsx b/src/components/newt-install-commands.tsx index 0d5ecad4c..6af0a86ae 100644 --- a/src/components/newt-install-commands.tsx +++ b/src/components/newt-install-commands.tsx @@ -18,6 +18,7 @@ import { FaLinux, FaWindows } from "react-icons/fa"; +import { ExternalLink } from "lucide-react"; import { SiKubernetes, SiNixos } from "react-icons/si"; export type CommandItem = string | { title: string; command: string }; @@ -333,31 +334,36 @@ WantedBy=default.target`

{t("commands")}

{platform === "kubernetes" && (

- For more and up to date Kubernetes installation - information, see{" "} - - docs.pangolin.net/manage/sites/install-kubernetes - - . + {t.rich("siteInstallKubernetesDocsDescription", { + docsLink: (chunks) => ( + + {chunks} + + + ) + })}

)} {platform === "advantech" && (

- For Advantech modem installation instructions, see{" "} - - docs.pangolin.net/manage/sites/install-advantech - - . + {t.rich("siteInstallAdvantechDocsDescription", { + docsLink: (chunks) => ( + + {chunks} + + + ) + })}

)}
diff --git a/src/components/resource-policy/CreatePolicyAuthMethodsSectionForm.tsx b/src/components/resource-policy/CreatePolicyAuthMethodsSectionForm.tsx index 30a87ffc8..c4d17e77f 100644 --- a/src/components/resource-policy/CreatePolicyAuthMethodsSectionForm.tsx +++ b/src/components/resource-policy/CreatePolicyAuthMethodsSectionForm.tsx @@ -385,7 +385,7 @@ export function CreatePolicyAuthMethodsSectionForm({ label={t( "headerAuthCompatibility" )} - info={t( + description={t( "headerAuthCompatibilityInfo" )} checked={field.value} @@ -426,7 +426,10 @@ export function CreatePolicyAuthMethodsSectionForm({ {/* Password row */}
@@ -456,7 +459,10 @@ export function CreatePolicyAuthMethodsSectionForm({ {/* Pincode row */}
@@ -484,7 +490,10 @@ export function CreatePolicyAuthMethodsSectionForm({ {/* Header auth row */}
diff --git a/src/components/resource-policy/EditPolicyAuthMethodsSectionForm.tsx b/src/components/resource-policy/EditPolicyAuthMethodsSectionForm.tsx index 034a1ca78..1fa241753 100644 --- a/src/components/resource-policy/EditPolicyAuthMethodsSectionForm.tsx +++ b/src/components/resource-policy/EditPolicyAuthMethodsSectionForm.tsx @@ -491,7 +491,7 @@ export function EditPolicyAuthMethodsSectionForm({ label={t( "headerAuthCompatibility" )} - info={t( + description={t( "headerAuthCompatibilityInfo" )} checked={field.value} diff --git a/src/components/resource-policy/ResourcePolicySubForms.tsx b/src/components/resource-policy/ResourcePolicySubForms.tsx index b9e825b7c..63f5b2a03 100644 --- a/src/components/resource-policy/ResourcePolicySubForms.tsx +++ b/src/components/resource-policy/ResourcePolicySubForms.tsx @@ -670,7 +670,7 @@ export function PolicyAuthMethodsSection({ label={t( "headerAuthCompatibility" )} - info={t( + description={t( "headerAuthCompatibilityInfo" )} checked={field.value}