diff --git a/src/app/[orgId]/settings/(private)/billing/page.tsx b/src/app/[orgId]/settings/(private)/billing/page.tsx index f77ae8589..a2557acf9 100644 --- a/src/app/[orgId]/settings/(private)/billing/page.tsx +++ b/src/app/[orgId]/settings/(private)/billing/page.tsx @@ -10,6 +10,8 @@ import { formatAxiosError } from "@app/lib/api"; import { AxiosResponse } from "axios"; import { SettingsContainer, + SettingsFormCell, + SettingsFormGrid, SettingsSection, SettingsSectionHeader, SettingsSectionTitle, @@ -1324,42 +1326,44 @@ export default function BillingPage() { -
-
-
-
- {t("billingCurrentKeys") || - "Current Keys"} -
-
- - {getLicenseKeyCount()} - - - {getLicenseKeyCount() === 1 - ? "key" - : "keys"} - + + +
+
+
+ {t("billingCurrentKeys") || + "Current Keys"} +
+
+ + {getLicenseKeyCount()} + + + {getLicenseKeyCount() === 1 + ? "key" + : "keys"} + +
+ +

+ {t( + "billingManageLicenseSubscriptionDescription" + ) || + "Manage your subscription for paid self-hosted license keys and download invoices."} +

- -

- {t( - "billingManageLicenseSubscriptionDescription" - ) || - "Manage your subscription for paid self-hosted license keys and download invoices."} -

-
-
+ + )} diff --git a/src/app/[orgId]/settings/clients/machine/create/page.tsx b/src/app/[orgId]/settings/clients/machine/create/page.tsx index a7ae6e107..4ad6a1673 100644 --- a/src/app/[orgId]/settings/clients/machine/create/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/create/page.tsx @@ -9,6 +9,8 @@ import { } from "@app/components/InfoSection"; import { SettingsContainer, + SettingsFormCell, + SettingsFormGrid, SettingsSection, SettingsSectionBody, SettingsSectionDescription, @@ -257,80 +259,87 @@ export default function Page() { e.preventDefault(); // block default enter refresh } }} - className="space-y-4 grid gap-4 grid-cols-1 md:grid-cols-2 items-start" id="create-client-form" > - ( - - - {t("name")} - - - - - - - {t( - "clientNameDescription" - )} - - - )} - /> -
- -
- {showAdvancedSettings && ( - ( - - - {t("clientAddress")} - - - + + ( + + + {t("name")} + + + + + + + {t( + "clientNameDescription" )} - {...field} - /> - - - - {t( - "addressDescription" - )} - - - )} - /> - )} + + + )} + /> + + + + + {showAdvancedSettings && ( + + ( + + + {t( + "clientAddress" + )} + + + + + + + {t( + "addressDescription" + )} + + + )} + /> + + )} + diff --git a/src/app/[orgId]/settings/resources/public/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/public/[niceId]/general/page.tsx index ed0061269..e288fb1d3 100644 --- a/src/app/[orgId]/settings/resources/public/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/public/[niceId]/general/page.tsx @@ -20,6 +20,8 @@ import { SettingsSectionBody, SettingsSectionDescription, SettingsSectionFooter, + SettingsFormCell, + SettingsFormGrid, SettingsSectionForm, SettingsSectionHeader, SettingsSectionTitle @@ -616,207 +618,225 @@ export default function GeneralForm() {
- ( - - - - form.setValue( - "enabled", - val - ) - } - /> - - - {t( - "disabledResourceDescription" - )} - - - - )} - /> - -
- ( - - - {t("name")} - - - - - - - )} - /> - - ( - - - {t("identifier")} - - - - - - - )} - /> -
- - {!["http", "ssh", "rdp", "vnc"].includes( - resource.mode - ) && ( - <> + + ( + name="enabled" + render={() => ( - - {t( - "resourcePortNumber" - )} - - - field.onChange( - e.target - .value - ? parseInt( - e - .target - .value - ) - : undefined + label={t( + "resourceEnable" + )} + onCheckedChange={( + val + ) => + form.setValue( + "enabled", + val ) } /> - {t( - "resourcePortNumberDescription" + "disabledResourceDescription" )} + )} /> - - )} + - {["http", "ssh", "rdp", "vnc"].includes( - resource.mode - ) && ( -
-
- { - if (res === null) { + + ( + + + {t("name")} + + + + + + + )} + /> + + + + ( + + + {t("identifier")} + + + + + + + )} + /> + + + {!["http", "ssh", "rdp", "vnc"].includes( + resource.mode + ) && ( + + ( + + + {t( + "resourcePortNumber" + )} + + + + field.onChange( + e + .target + .value + ? parseInt( + e + .target + .value + ) + : undefined + ) + } + /> + + + + {t( + "resourcePortNumberDescription" + )} + + + )} + /> + + )} + + {["http", "ssh", "rdp", "vnc"].includes( + resource.mode + ) && ( + +
+ { + if (res === null) { + form.setValue( + "domainId", + undefined + ); + form.setValue( + "subdomain", + undefined + ); + setResourceFullDomain( + `${resource.ssl ? "https" : "http"}://` + ); + return; + } form.setValue( "domainId", - undefined + res.domainId ); form.setValue( "subdomain", - undefined + res.subdomain ?? + undefined ); setResourceFullDomain( - `${resource.ssl ? "https" : "http"}://` + `${resource.ssl ? "https" : "http"}://${toUnicode(res.fullDomain)}` ); - return; + }} + /> +
+
+ )} + {showResourcePolicy && ( + +
+ + {t("sharedPolicy")} + + -
-
- )} - {showResourcePolicy && ( -
- - {t("sharedPolicy")} - - -
- )} + orgId={org.org.orgId} + value={ + selectedSharedPolicyId + } + onChange={ + setSelectedSharedPolicyId + } + /> +
+ + )} +
diff --git a/src/app/[orgId]/settings/resources/public/[niceId]/ssh/page.tsx b/src/app/[orgId]/settings/resources/public/[niceId]/ssh/page.tsx index f0e856f69..c0f4655c9 100644 --- a/src/app/[orgId]/settings/resources/public/[niceId]/ssh/page.tsx +++ b/src/app/[orgId]/settings/resources/public/[niceId]/ssh/page.tsx @@ -5,6 +5,8 @@ import { SettingsSection, SettingsSectionBody, SettingsSectionDescription, + SettingsFormCell, + SettingsFormGrid, SettingsSectionForm, SettingsSectionHeader, SettingsSectionTitle, @@ -410,174 +412,199 @@ function SshServerForm({
-
-

{t("sshServerMode")}

- - {sshServerMode == "standard" - ? t("sshServerModeStandard") - : t("sshServerModePangolin")} - -
+ + +
+

+ {t("sshServerMode")} +

+ + {sshServerMode == "standard" + ? t("sshServerModeStandard") + : t("sshServerModePangolin")} + +
+
-
-

{t("sshAuthenticationMethod")}

- - value={pamMode} - options={authMethodOptions} - onChange={(value) => - form.setValue("pamMode", value, { - shouldValidate: true - }) - } - cols={2} - /> -
+ +
+

+ {t("sshAuthenticationMethod")} +

+ + value={pamMode} + options={authMethodOptions} + onChange={(value) => + form.setValue("pamMode", value, { + shouldValidate: true + }) + } + cols={2} + /> +
+
- {showDaemonLocation && ( -
-

{t("sshAuthDaemonLocation")}

- - value={standardDaemonLocation} - options={daemonLocationOptions} - onChange={(value) => - form.setValue( - "standardDaemonLocation", - value, - { shouldValidate: true } - ) - } - cols={2} - /> -

- {t("sshDaemonDisclaimer")}{" "} - - {t("learnMore")} - - -

-
- )} - - {showDaemonPort && ( -
- ( - - - {t("sshDaemonPort")} - - - - - - - )} - /> -
- )} - -
- - - {t("sshServerDestination")} - - - {t("sshServerDestinationDescription")} - - - {isNative ? ( - ( - - +
+

+ {t("sshAuthDaemonLocation")} +

+ + value={standardDaemonLocation} + options={daemonLocationOptions} + onChange={(value) => + form.setValue( + "standardDaemonLocation", + value, + { + shouldValidate: true + } + ) + } + cols={2} + /> +

+ {t("sshDaemonDisclaimer")}{" "} + - - - - - - - { - form.setValue( - "selectedNativeSite", - site, - { - shouldValidate: - true - } - ); - setNativeSiteOpen( - false - ); - }} - /> - - - - - )} - /> - ) : useMultiSiteTargetForm ? ( - - ) : ( - + {t("learnMore")} + + +

+
+ )} -
+ + {showDaemonPort && ( + + ( + + + {t("sshDaemonPort")} + + + + + + + )} + /> + + )} + + + + + {t("sshServerDestination")} + + + {t( + "sshServerDestinationDescription" + )} + + + + + {isNative ? ( + + ( + + + + + + + + + { + form.setValue( + "selectedNativeSite", + site, + { + shouldValidate: + true + } + ); + setNativeSiteOpen( + false + ); + }} + /> + + + + + )} + /> + + ) : useMultiSiteTargetForm ? ( + + + + ) : ( + + + + )} +
diff --git a/src/app/[orgId]/settings/resources/public/create/page.tsx b/src/app/[orgId]/settings/resources/public/create/page.tsx index 1662ee560..ff4fd938b 100644 --- a/src/app/[orgId]/settings/resources/public/create/page.tsx +++ b/src/app/[orgId]/settings/resources/public/create/page.tsx @@ -4,6 +4,8 @@ import CopyTextBox from "@app/components/CopyTextBox"; import DomainPicker from "@app/components/DomainPicker"; import { SettingsContainer, + SettingsFormCell, + SettingsFormGrid, SettingsSection, SettingsSectionBody, SettingsSectionDescription, @@ -806,172 +808,198 @@ export default function Page() { - {/* Name */} - - { - if (e.key === "Enter") { - e.preventDefault(); - } - }} - className="grid gap-4 grid-cols-1 md:grid-cols-2 items-start" - id="base-resource-form" - > - ( - - - {t("name")} - - - - - - - {t( - "resourceNameDescription" - )} - - - )} - /> - - + + +
+ { + if ( + e.key === + "Enter" + ) { + e.preventDefault(); + } + }} + id="base-resource-form" + > + ( + + + {t( + "name" + )} + + + + + + + {t( + "resourceNameDescription" + )} + + + )} + /> + + +
- {/* Inline Type Selector */} -
-

- {t("type")} -

- - options={typeOptions} - value={resourceType} - onChange={setResourceType} - cols={6} - /> -

- {t("resourceTypeDescription")} -

-
- - {/* Domain/Subdomain (HTTP-based types) */} - {isHttpResource && ( -
- ( - - = - 1 - } - onDomainChange={( - res - ) => { - if (!res) - return; - httpForm.setValue( - "subdomain", - res.subdomain, - { - shouldValidate: - true - } - ); - httpForm.setValue( - "domainId", - res.domainId, - { - shouldValidate: - true - } - ); - }} - /> - - - {t( - "resourceDomainDescription" - )} - - - )} - /> - - )} - - {/* Proxy Port (TCP/UDP types) */} - {!isHttpResource && ( -
- { - if (e.key === "Enter") { - e.preventDefault(); + +
+

+ {t("type")} +

+ + options={typeOptions} + value={resourceType} + onChange={ + setResourceType } - }} - className="grid gap-4 grid-cols-1 md:grid-cols-2 items-start" - id="tcp-udp-settings-form" - > - ( - - - {t( - "resourcePortNumber" - )} - - - - field.onChange( - e - .target - .value - ? parseInt( - e - .target - .value - ) - : undefined - ) - } - /> - - - - {t( - "resourcePortDescription" - )} - - - )} + cols={6} /> - - - )} +

+ {t( + "resourceTypeDescription" + )} +

+
+
+ + {isHttpResource && ( + +
+ ( + + = + 1 + } + onDomainChange={( + res + ) => { + if ( + !res + ) + return; + httpForm.setValue( + "subdomain", + res.subdomain, + { + shouldValidate: + true + } + ); + httpForm.setValue( + "domainId", + res.domainId, + { + shouldValidate: + true + } + ); + }} + /> + + + {t( + "resourceDomainDescription" + )} + + + )} + /> + +
+ )} + + {!isHttpResource && ( + +
+ { + if ( + e.key === + "Enter" + ) { + e.preventDefault(); + } + }} + id="tcp-udp-settings-form" + > + ( + + + {t( + "resourcePortNumber" + )} + + + + field.onChange( + e + .target + .value + ? parseInt( + e + .target + .value + ) + : undefined + ) + } + /> + + + + {t( + "resourcePortDescription" + )} + + + )} + /> + + +
+ )} +
@@ -1005,202 +1033,237 @@ export default function Page() { > - {/* Mode */} -
-

{t("sshServerMode")}

- - value={sshServerMode} - options={sshModeOptions} - onChange={setSshServerMode} - cols={2} - /> -
- -
-

{t("sshAuthenticationMethod")}

- - value={pamMode} - options={ - authMethodOptions - } - onChange={setPamMode} - cols={2} - /> -
- - {/* Daemon Location (standard + push) */} - {showDaemonLocation && ( -
-

{t("sshAuthDaemonLocation")}

- - value={ - standardDaemonLocation - } - options={ - daemonLocationOptions - } - onChange={ - setStandardDaemonLocation - } - cols={2} - /> -

- {t( - "sshDaemonDisclaimer" - )}{" "} - + +

- )} - - {/* Daemon Port (standard + push + remote) */} - {showDaemonPort && ( -
-
- ( - - - {t( - "sshDaemonPort" - )} - - - - - - - )} + onChange={ + setSshServerMode + } + cols={2} />
-
- )} + - {/* Server Destination */} -
- - - {t( - "sshServerDestination" - )} - - - {t( - "sshServerDestinationDescription" - )} - - - {isNative ? ( - - - - - - { - setNativeSelectedSite( - site - ); - setNativeSiteOpen( - false - ); - }} + onChange={ + setStandardDaemonLocation + } + cols={2} /> - - +

+ {t( + "sshDaemonDisclaimer" + )}{" "} + + {t( + "learnMore" + )} + + +

+
+ + )} + + {showDaemonPort && ( + +
+ ( + + + {t( + "sshDaemonPort" + )} + + + + + + + )} + /> + +
+ )} + + + + + {t( + "sshServerDestination" + )} + + + {t( + "sshServerDestinationDescription" + )} + + + + + {isNative ? ( + + + + + + + { + setNativeSelectedSite( + site + ); + setNativeSiteOpen( + false + ); + }} + /> + + + ) : standardDaemonLocation !== "site" || pamMode === "passthrough" ? ( -
- - + +
+ + +
) : ( -
- - + +
+ + +
)} -
+
diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx index ab97197a3..e1ce91919 100644 --- a/src/app/[orgId]/settings/sites/create/page.tsx +++ b/src/app/[orgId]/settings/sites/create/page.tsx @@ -2,6 +2,8 @@ import { SettingsContainer, + SettingsFormCell, + SettingsFormGrid, SettingsSection, SettingsSectionBody, SettingsSectionDescription, @@ -514,98 +516,107 @@ export default function Page() { e.preventDefault(); // block default enter refresh } }} - className="space-y-4 grid gap-4 grid-cols-1 md:grid-cols-2 items-start" id="create-site-form" > - ( - - - {t("name")} - - - - - - - {t( - "siteNameDescription" - )} - - - )} - /> - {form.watch("method") === "newt" && ( -
- -
- )} - {form.watch("method") === "newt" && - showAdvancedSettings && ( + + ( - + - {t( - "siteAddress" - )} + {t("name")} { - setClientAddress( - e - .target - .value - ); - field.onChange( - e - .target - .value - ); - }} + {...field} /> {t( - "siteAddressDescription" + "siteNameDescription" )} )} /> - )} + {form.watch("method") === + "newt" && ( + <> + + {showAdvancedSettings && ( + ( + + + {t( + "siteAddress" + )} + + + { + setClientAddress( + e + .target + .value + ); + field.onChange( + e + .target + .value + ); + }} + /> + + + + {t( + "siteAddressDescription" + )} + + + )} + /> + )} + + )} + + diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 09fc8b0af..892766c6d 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -43,6 +43,49 @@ export function SettingsSectionForm({ ); } +export function SettingsFormGrid({ + children, + className +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + +export function SettingsFormCell({ + children, + span = "half", + className +}: { + children: React.ReactNode; + span?: "quarter" | "half" | "full"; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + export function SettingsSectionTitle({ children }: { diff --git a/src/components/resource-policy/PolicyAuthStackSectionCreate.tsx b/src/components/resource-policy/PolicyAuthStackSectionCreate.tsx index 07312f646..5c3738e84 100644 --- a/src/components/resource-policy/PolicyAuthStackSectionCreate.tsx +++ b/src/components/resource-policy/PolicyAuthStackSectionCreate.tsx @@ -1,6 +1,8 @@ "use client"; import { + SettingsFormCell, + SettingsFormGrid, SettingsSection, SettingsSectionBody, SettingsSectionDescription, @@ -111,65 +113,67 @@ export function PolicyAuthStackSectionCreate({ -
- - parentForm.setValue("sso", active) - } - skipToIdpId={skipToIdpId} - onSkipToIdpChange={(id) => - parentForm.setValue("skipToIdpId", id) - } - allIdps={allIdps} - rolesEditor={ - - control={parentForm.control} - name="roles" - render={({ field }) => ( - - field.onChange(newRoles) - } - autocompleteOptions={allRoles} - allowDuplicates={false} - size="sm" - /> - )} - /> - } - usersEditor={ - - control={parentForm.control} - name="users" - render={({ field }) => ( - - field.onChange(newUsers) - } - autocompleteOptions={allUsers} - allowDuplicates={false} - size="sm" - /> - )} - /> - } - /> -
+ + + + parentForm.setValue("sso", active) + } + skipToIdpId={skipToIdpId} + onSkipToIdpChange={(id) => + parentForm.setValue("skipToIdpId", id) + } + allIdps={allIdps} + rolesEditor={ + + control={parentForm.control} + name="roles" + render={({ field }) => ( + + field.onChange(newRoles) + } + autocompleteOptions={allRoles} + allowDuplicates={false} + size="sm" + /> + )} + /> + } + usersEditor={ + + control={parentForm.control} + name="users" + render={({ field }) => ( + + field.onChange(newUsers) + } + autocompleteOptions={allUsers} + allowDuplicates={false} + size="sm" + /> + )} + /> + } + /> + + diff --git a/src/components/resource-policy/PolicyAuthStackSectionEdit.tsx b/src/components/resource-policy/PolicyAuthStackSectionEdit.tsx index 45f5e22d1..f3038c9fa 100644 --- a/src/components/resource-policy/PolicyAuthStackSectionEdit.tsx +++ b/src/components/resource-policy/PolicyAuthStackSectionEdit.tsx @@ -1,6 +1,8 @@ "use client"; import { + SettingsFormCell, + SettingsFormGrid, SettingsSection, SettingsSectionBody, SettingsSectionDescription, @@ -475,101 +477,109 @@ export function PolicyAuthStackSectionEdit({ {isResourceOverlay && ( )} -
- - form.setValue("sso", active) - } - skipToIdpId={skipToIdpId} - onSkipToIdpChange={(id) => - form.setValue("skipToIdpId", id) - } - allIdps={allIdps} - disabled={authReadonly} - idpDisabled={authReadonly} - rolesEditor={ - isResourceOverlay ? ( - - setCombinedRoles( - selected.map( - (role) => ({ - ...role, - isAdmin: - Boolean( - role.isAdmin - ) - }) + + + + form.setValue("sso", active) + } + skipToIdpId={skipToIdpId} + onSkipToIdpChange={(id) => + form.setValue("skipToIdpId", id) + } + allIdps={allIdps} + disabled={authReadonly} + idpDisabled={authReadonly} + rolesEditor={ + isResourceOverlay ? ( + + setCombinedRoles( + selected.map( + (role) => ({ + ...role, + isAdmin: + Boolean( + role.isAdmin + ) + }) + ) ) - ) - } - disabled={isLoading} - restrictAdminRole - lockedIds={policyRoleLockedIds} - /> - ) : ( - ( - - form.setValue( - "roles", + } + disabled={isLoading} + restrictAdminRole + lockedIds={ + policyRoleLockedIds + } + /> + ) : ( + ( + - )} - /> - ) - } - usersEditor={ - isResourceOverlay ? ( - - ) : ( - ( - - form.setValue( - "users", + ) => + form.setValue( + "roles", + selected + ) + } + disabled={readonly} + restrictAdminRole + /> + )} + /> + ) + } + usersEditor={ + isResourceOverlay ? ( + + ) : ( + ( + - )} - /> - ) - } - /> -
+ ) => + form.setValue( + "users", + selected + ) + } + disabled={readonly} + /> + )} + /> + ) + } + /> + + diff --git a/src/components/shared-policy-selector.tsx b/src/components/shared-policy-selector.tsx index 5eb4d0013..a4089df2d 100644 --- a/src/components/shared-policy-selector.tsx +++ b/src/components/shared-policy-selector.tsx @@ -183,7 +183,7 @@ export function SharedPolicySelect({ role="combobox" disabled={disabled} className={cn( - "w-full justify-between font-normal md:w-1/2", + "w-full justify-between font-normal", value !== null && !resolvedLabel && !fetchedPolicy?.name &&