From c41bd98f075464dccd38e57071e026f4b60c1d4e Mon Sep 17 00:00:00 2001 From: marcone tenorio Date: Thu, 30 Apr 2026 13:29:58 +0200 Subject: [PATCH 1/3] fix(opcua): align UI with design system Mirror of openplc-web fix/ui-opcua-design-system to keep shared frontend surfaces byte-identical. Updates the OPC UA address-space tree, selected variables list, certificate/security/user tabs and modals so that: - Variable tree uses Radix Chevron icons instead of glyphs that were falling back to the color emoji font. - Type badges in the variable tree and selected-variables list are neutralized to a single palette. - Delete/Remove buttons across the OPC UA tabs use the neutral box styling consistent with adjacent Edit actions. - Inline alert and validation-error containers drop the amber/red backgrounds in favour of neutral border + background, with copy carrying the severity cue. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/certificate-modal.tsx | 4 +- .../components/certificates-tab.tsx | 2 +- .../components/security-profile-modal.tsx | 4 +- .../components/security-profiles-tab.tsx | 10 +-- .../components/selected-variables-list.tsx | 35 +++----- .../opcua-server/components/user-modal.tsx | 6 +- .../opcua-server/components/users-tab.tsx | 6 +- .../components/variable-config-modal.tsx | 6 +- .../opcua-server/components/variable-tree.tsx | 80 +++++++------------ 9 files changed, 59 insertions(+), 94 deletions(-) diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificate-modal.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificate-modal.tsx index bf4712671..825ce7b66 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificate-modal.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificate-modal.tsx @@ -226,10 +226,10 @@ MIIEpDCCAowCCQC7... {/* Validation Errors */} {validationErrors.length > 0 && ( -
+
    {validationErrors.map((error, index) => ( -
  • +
  • {error}
  • ))} diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx index b8974c6fe..0135da104 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx @@ -284,7 +284,7 @@ MIIEvgIBADANBg... diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profile-modal.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profile-modal.tsx index 93389338e..84996e0c0 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profile-modal.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profile-modal.tsx @@ -351,10 +351,10 @@ export const SecurityProfileModal = ({ {/* Validation Errors */} {validationErrors.length > 0 && ( -
    +
      {validationErrors.map((error, index) => ( -
    • +
    • {error}
    • ))} diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx index 0315a564f..6ea0f49bd 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx @@ -104,8 +104,8 @@ export const SecurityProfilesTab = ({ config, serverName, onConfigChange }: Secu {/* Warning if no profiles are enabled */} {!hasEnabledProfile && ( -
      -

      +

      +

      Warning: At least one security profile must be enabled for clients to connect.

      @@ -172,7 +172,7 @@ export const SecurityProfilesTab = ({ config, serverName, onConfigChange }: Secu onClick={() => handleDeleteProfile(profile.id)} disabled={config.securityProfiles.length <= 1} className={cn( - 'h-[28px] rounded-md border border-red-300 bg-white px-3 font-caption text-xs font-medium text-red-600 hover:bg-red-50 dark:border-red-800 dark:bg-neutral-800 dark:text-red-400 dark:hover:bg-red-950', + 'h-[28px] rounded-md border border-neutral-300 bg-white px-3 font-caption text-xs font-medium text-neutral-700 hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-700', config.securityProfiles.length <= 1 && 'cursor-not-allowed opacity-50', )} > @@ -197,8 +197,8 @@ export const SecurityProfilesTab = ({ config, serverName, onConfigChange }: Secu {/* Warning for insecure profiles */} {profile.securityPolicy === 'None' && profile.enabled && ( -
      -

      +

      +

      Warning: No encryption or authentication. Use only for development/testing.

      diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/selected-variables-list.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/selected-variables-list.tsx index f74dc9a6d..0493f3381 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/selected-variables-list.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/selected-variables-list.tsx @@ -6,31 +6,18 @@ interface SelectedVariablesListProps { onRemove: (nodeId: string) => void } -// Get icon component for node type -const NodeTypeIcon = ({ nodeType }: { nodeType: OpcUaNodeConfig['nodeType'] }) => { - switch (nodeType) { - case 'structure': - return ( - - S - - ) - case 'array': - return ( - - [] - - ) - case 'variable': - default: - return ( - - V - - ) - } +const NODE_TYPE_LABEL: Record, string> = { + structure: 'S', + array: '[]', + variable: 'V', } +const NodeTypeIcon = ({ nodeType }: { nodeType: OpcUaNodeConfig['nodeType'] }) => ( + + {NODE_TYPE_LABEL[nodeType] ?? 'V'} + +) + // Format permissions for display const formatPermissions = (permissions: OpcUaNodeConfig['permissions']): string => { return `V:${permissions.viewer} O:${permissions.operator} E:${permissions.engineer}` @@ -83,7 +70,7 @@ export const SelectedVariablesList = ({ nodes, onEdit, onRemove }: SelectedVaria diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx index 533d199a1..9cfe190a7 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx @@ -330,7 +330,7 @@ export const UserModal = ({ {availableCertificates.length === 0 ? ( -

      +

      No trusted certificates available. Add certificates in the Certificates tab first.

      ) : ( @@ -418,10 +418,10 @@ export const UserModal = ({ {/* Validation Errors */} {validationErrors.length > 0 && ( -
      +
        {validationErrors.map((error, index) => ( -
      • +
      • {error}
      • ))} diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx index 41d1340c7..36b63156c 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx @@ -117,8 +117,8 @@ export const UsersTab = ({ config, serverName, onConfigChange }: UsersTabProps) {/* Warning if username auth is enabled but no users */} {usernameAuthEnabled && passwordUserCount === 0 && ( -
        -

        +

        +

        Warning: Username authentication is enabled but no password users exist. Add at least one user for clients to authenticate.

        @@ -180,7 +180,7 @@ export const UsersTab = ({ config, serverName, onConfigChange }: UsersTabProps) diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-config-modal.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-config-modal.tsx index 2b0ecbbb5..412e56bc0 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-config-modal.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-config-modal.tsx @@ -656,7 +656,7 @@ export const VariableConfigModal = ({ {/* Structure Info */} {isStructureOrFb && variable?.structureInfo && ( -
        +

        Structure Information

        @@ -751,10 +751,10 @@ export const VariableConfigModal = ({ {/* Validation Errors */} {validationErrors.length > 0 && ( -
        +
          {validationErrors.map((error, index) => ( -
        • +
        • {error}
        • ))} diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-tree.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-tree.tsx index 05fc036a6..df28ddf71 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-tree.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/variable-tree.tsx @@ -1,3 +1,4 @@ +import { ChevronDownIcon, ChevronRightIcon } from '@radix-ui/react-icons' import { cn } from '@root/frontend/utils/cn' import { useCallback, useState } from 'react' @@ -10,60 +11,37 @@ interface VariableTreeProps { filter?: string } -// Icons for different node types (using text abbreviations) +const NODE_TYPE_LABEL: Record = { + program: 'P', + function_block: 'FB', + global: 'G', + structure: 'S', + array: '[]', + variable: 'V', +} + const NodeIcon = ({ type }: { type: VariableTreeNode['type'] }) => { - switch (type) { - case 'program': - return ( - - P - - ) - case 'function_block': - return ( - - FB - - ) - case 'global': - return ( - - G - - ) - case 'structure': - return ( - - S - - ) - case 'array': - return ( - - [] - - ) - case 'variable': - return ( - - V - - ) - default: - return null - } + const label = NODE_TYPE_LABEL[type] + if (!label) return null + return ( + + {label} + + ) } -// Expand/collapse icon -const ExpandIcon = ({ expanded, onClick }: { expanded: boolean; onClick: (e: React.MouseEvent) => void }) => ( - -) +const ExpandIcon = ({ expanded, onClick }: { expanded: boolean; onClick: (e: React.MouseEvent) => void }) => { + const Icon = expanded ? ChevronDownIcon : ChevronRightIcon + return ( + + ) +} // Checkbox for selection const Checkbox = ({ From deabccb4b4f29f642b619334819c3db0966cfd72 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 4 May 2026 20:05:09 -0400 Subject: [PATCH 2/3] fix(opcua): align tabs with S7Comm design system Convert Security Profiles, Users, and Trusted Client Certificates lists to tables matching the S7Comm Data Blocks pattern, with the brand-blue "Add" action button and edit/delete icon buttons. In the user modal, move per-field validation errors inline beneath each input and drop the bottom validation card. On the Users tab, drop the colored role badges in favor of plain text. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/certificates-tab.tsx | 108 +++++----- .../components/security-profiles-tab.tsx | 192 +++++++++--------- .../opcua-server/components/user-modal.tsx | 147 +++++++------- .../opcua-server/components/users-tab.tsx | 150 ++++++-------- 4 files changed, 284 insertions(+), 313 deletions(-) diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx index 0135da104..f2a0b23e4 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/certificates-tab.tsx @@ -1,3 +1,4 @@ +import { PlusIcon, TrashIcon } from '@radix-ui/react-icons' import { useOpenPLCStore } from '@root/frontend/store' import { cn } from '@root/frontend/utils/cn' import type { OpcUaServerConfig, OpcUaTrustedCertificate } from '@root/middleware/shared/ports/types' @@ -250,67 +251,64 @@ MIIEvgIBADANBg... - {/* Certificates List */} + {/* Certificates Table */} {config.security.trustedClientCertificates.length === 0 ? ( -
          -

          - No trusted certificates configured. Add certificates to enable certificate-based authentication. -

          -
          +

          + No trusted certificates configured. Add certificates to enable certificate-based authentication. +

          ) : ( -
          - {config.security.trustedClientCertificates.map((cert) => ( -
          - {/* Header row with icon, name, and actions */} -
          -
          - 📜 - - {cert.id} - -
          - - {/* Actions */} -
          - -
          -
          - - {/* Certificate Details */} -
          - {cert.subject && ( -

          - Subject: {cert.subject} -

          - )} - {cert.validFrom && cert.validTo && ( -

          - Valid: {cert.validFrom} to {cert.validTo} -

          - )} - {cert.fingerprint && ( -

          - Fingerprint: {cert.fingerprint} -

          - )} -
          -
          - ))} +
          + + + + + + + + + + + + {config.security.trustedClientCertificates.map((cert) => ( + + + + + + + + ))} + +
          ID + Subject + + Valid + + Fingerprint + + Actions +
          {cert.id}{cert.subject || '-'} + {cert.validFrom && cert.validTo ? `${cert.validFrom} to ${cert.validTo}` : '-'} + + {cert.fingerprint || '-'} + +
          + +
          +
          )}
          diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx index 6ea0f49bd..5aa6a9e4b 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx @@ -1,3 +1,4 @@ +import { Pencil1Icon, PlusIcon, TrashIcon } from '@radix-ui/react-icons' import { useOpenPLCStore } from '@root/frontend/store' import { cn } from '@root/frontend/utils/cn' import type { OpcUaSecurityProfile, OpcUaServerConfig } from '@root/middleware/shared/ports/types' @@ -11,17 +12,6 @@ interface SecurityProfilesTabProps { onConfigChange: () => void } -// Helper to get a human-readable description for a security profile -const getProfileDescription = (profile: OpcUaSecurityProfile): string => { - if (profile.securityPolicy === 'None') { - return 'No encryption or authentication. Use only for development/testing.' - } - if (profile.securityMode === 'Sign') { - return 'Messages are signed but not encrypted.' - } - return 'Full security: messages are signed and encrypted.' -} - // Helper to format auth methods for display const formatAuthMethods = (methods: OpcUaSecurityProfile['authMethods']): string => { return methods.join(', ') @@ -115,98 +105,102 @@ export const SecurityProfilesTab = ({ config, serverName, onConfigChange }: Secu - {/* Security Profiles List */} -
          - {config.securityProfiles.map((profile) => ( -
          - {/* Header row with toggle, name, and actions */} -
          -
          - {/* Enable Toggle */} -
          - - {/* Profile Details */} -
          -

          - Policy: {profile.securityPolicy} - {' | '} - Mode: {profile.securityMode} -

          -

          - Authentication: {formatAuthMethods(profile.authMethods)} -

          -

          - {getProfileDescription(profile)} -

          - - {/* Warning for insecure profiles */} - {profile.securityPolicy === 'None' && profile.enabled && ( -
          -

          - Warning: No encryption or authentication. Use only for development/testing. -

          -
          - )} -
          -
          - ))} -
          + {/* Security Profiles Table */} + {config.securityProfiles.length > 0 ? ( +
          + + + + + + + + + + + + + {config.securityProfiles.map((profile) => { + const isLastEnabled = + profile.enabled && config.securityProfiles.filter((p) => p.enabled).length <= 1 + return ( + + + + + + + + + ) + })} + +
          + Enabled + Name + Policy + Mode + Authentication + + Actions +
          + {profile.name} + {profile.securityPolicy} + {profile.securityMode} + {formatAuthMethods(profile.authMethods)} + +
          + + +
          +
          +
          + ) : ( +

          + No security profiles configured. Add a profile to allow clients to connect. +

          + )} {/* Note about certificates */}
          diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx index 9cfe190a7..de24c240a 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx @@ -121,62 +121,66 @@ export const UserModal = ({ ) }, [trustedCertificates, usedCertificateIds, existingUser]) - // Validation rules - const validationErrors = useMemo(() => { - const errors: string[] = [] - - if (authType === 'password') { - // Username validation - if (!username.trim()) { - errors.push('Username is required') - } else if (username.length > 64) { - errors.push('Username must be 64 characters or less') - } else { - // Check for duplicate username (case insensitive), excluding current user when editing - const isDuplicate = existingUsernames.some( - (existingName) => - existingName.toLowerCase() === username.trim().toLowerCase() && - (!existingUser || existingUser.username?.toLowerCase() !== existingName.toLowerCase()), - ) - if (isDuplicate) { - errors.push('A user with this username already exists') - } - } - - // Password validation (only required for new users or when changing password) - if (!isEditing || password) { - if (!password) { - errors.push('Password is required') - } else if (password.length < 4) { - errors.push('Password must be at least 4 characters') - } else if (password !== confirmPassword) { - errors.push('Passwords do not match') - } - } - } else { - // Certificate validation - if (!certificateId) { - errors.push('Please select a certificate') - } - if (availableCertificates.length === 0 && !existingUser?.certificateId) { - errors.push('No trusted certificates available. Add certificates in the Certificates tab first.') - } + // Per-field validation. Each error string surfaces inline under its + // corresponding input rather than in a bottom validation card — same + // pattern the password-mismatch label uses. + // + // The mismatch and "passwords-not-yet-confirmed" gates live in their + // own flags below since they have different visibility rules from + // the field-error pattern (mismatch is only shown once the user has + // typed in confirmPassword; missing confirmPassword silently + // disables Save without a label). + + const usernameError = useMemo(() => { + if (authType !== 'password') return null + if (!username.trim()) return 'Username is required' + if (username.length > 64) return 'Username must be 64 characters or less' + const isDuplicate = existingUsernames.some( + (existingName) => + existingName.toLowerCase() === username.trim().toLowerCase() && + (!existingUser || existingUser.username?.toLowerCase() !== existingName.toLowerCase()), + ) + if (isDuplicate) return 'A user with this username already exists' + return null + }, [authType, username, existingUsernames, existingUser]) + + const passwordError = useMemo(() => { + if (authType !== 'password') return null + // Editing path: an empty password means "keep existing" — not an error. + if (isEditing && !password) return null + if (!password) return 'Password is required' + if (password.length < 4) return 'Password must be at least 4 characters' + return null + }, [authType, password, isEditing]) + + const certificateError = useMemo(() => { + if (authType !== 'certificate') return null + if (availableCertificates.length === 0 && !existingUser?.certificateId) { + // The certificate-auth section already renders this banner inline + // when there are no trusted certs. Skip surfacing it as a duplicate + // field error. + return null } - - return errors - }, [ - authType, - username, - password, - confirmPassword, - certificateId, - existingUsernames, - existingUser, - isEditing, - availableCertificates, - ]) - - const isValid = validationErrors.length === 0 + if (!certificateId) return 'Please select a certificate' + return null + }, [authType, certificateId, availableCertificates, existingUser]) + + // Password mismatch handling has two flavours: + // - `passwordsMismatchVisible`: drives the inline red label under the + // Confirm Password input. Only true once the user has typed something + // in confirmPassword, so the error doesn't flicker while they're still + // filling out the form. + // - `passwordsOk`: gates the Save button. Requires both fields to be + // filled AND match when a password is being set (new user, or editing + // user with non-empty password). Empty confirmPassword keeps Save + // disabled silently — no error is shown until the user types into the + // confirm field. + const isSettingPassword = authType === 'password' && (!isEditing || password.length > 0) + const passwordsMismatchVisible = + isSettingPassword && confirmPassword.length > 0 && password !== confirmPassword + const passwordsOk = !isSettingPassword || (confirmPassword.length > 0 && password === confirmPassword) + + const isValid = !usernameError && !passwordError && !certificateError && passwordsOk // Handle save const handleSave = useCallback(async () => { @@ -266,6 +270,11 @@ export const UserModal = ({ maxLength={64} className={inputStyles} /> + {usernameError && ( + + {usernameError} + + )}
          {/* Password */} @@ -306,6 +315,11 @@ export const UserModal = ({ )}
          + {passwordError && ( + + {passwordError} + + )}
        {/* Confirm Password */} @@ -318,6 +332,11 @@ export const UserModal = ({ placeholder='••••••••' className={inputStyles} /> + {passwordsMismatchVisible && ( + + Passwords do not match + + )}
        )} @@ -362,6 +381,11 @@ export const UserModal = ({ Select from trusted certificates configured in the Certificates tab + {certificateError && ( + + {certificateError} + + )}
        )}
        @@ -415,19 +439,6 @@ export const UserModal = ({ ))}
      - - {/* Validation Errors */} - {validationErrors.length > 0 && ( -
      -
        - {validationErrors.map((error, index) => ( -
      • - {error} -
      • - ))} -
      -
      - )}
      diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx index 36b63156c..2a45f6823 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx @@ -1,5 +1,5 @@ +import { Pencil1Icon, PlusIcon, TrashIcon } from '@radix-ui/react-icons' import { useOpenPLCStore } from '@root/frontend/store' -import { cn } from '@root/frontend/utils/cn' import type { OpcUaServerConfig, OpcUaUser } from '@root/middleware/shared/ports/types' import { useCallback, useMemo, useState } from 'react' @@ -11,28 +11,10 @@ interface UsersTabProps { onConfigChange: () => void } -// Helper to get role display info -const getRoleInfo = (role: OpcUaUser['role']): { label: string; description: string; color: string } => { - switch (role) { - case 'viewer': - return { - label: 'Viewer', - description: 'Read-only access', - color: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300', - } - case 'operator': - return { - label: 'Operator', - description: 'Read/Write per variable permissions', - color: 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300', - } - case 'engineer': - return { - label: 'Engineer', - description: 'Full access', - color: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300', - } - } +const ROLE_LABELS: Record = { + viewer: 'Viewer', + operator: 'Operator', + engineer: 'Engineer', } // Helper to get user display name @@ -129,80 +111,66 @@ export const UsersTab = ({ config, serverName, onConfigChange }: UsersTabProps) - {/* Users List */} + {/* Users Table */} {config.users.length === 0 ? ( -
      -

      - No users configured. Add users for password or certificate authentication. -

      -
      +

      + No users configured. Add users for password or certificate authentication. +

      ) : ( -
      - {config.users.map((user) => { - const roleInfo = getRoleInfo(user.role) - return ( -
      - {/* Header row with icon, name, and actions */} -
      -
      - {/* User Icon */} - {user.type === 'password' ? '👤' : '🔐'} - - {/* User Name */} - - {getUserDisplayName(user)} - - - {/* Role Badge */} - - {roleInfo.label} - -
      - - {/* Actions */} -
      - - -
      -
      - - {/* User Details */} -
      -

      - Type:{' '} - {user.type === 'password' ? 'Password Authentication' : 'Certificate Authentication'} -

      - {user.type === 'certificate' && user.certificateId && ( -

      - Certificate: {user.certificateId} -

      - )} -

      {roleInfo.description}

      -
      -
      - ) - })} +
      + + + + + + + + + + + {config.users.map((user) => ( + + + + + + + ))} + +
      NameTypeRole + Actions +
      + {getUserDisplayName(user)} + + {user.type === 'password' ? 'Password' : 'Certificate'} + + {ROLE_LABELS[user.role]} + +
      + + +
      +
      )} From 84525cd0a3ffe6490fb21ae2be2fe8e8c7cf3dcd Mon Sep 17 00:00:00 2001 From: marcone tenorio Date: Tue, 5 May 2026 10:26:00 +0200 Subject: [PATCH 3/3] style(opcua): apply prettier formatting Collapses short multi-line expressions in security-profiles-tab, user-modal and users-tab so prettier --check passes. Mirrored across both repos to keep ci-sync byte-identical. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/security-profiles-tab.tsx | 3 +-- .../opcua-server/components/user-modal.tsx | 19 +++++-------------- .../opcua-server/components/users-tab.tsx | 4 +--- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx index 5aa6a9e4b..9e30b59b4 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/security-profiles-tab.tsx @@ -135,8 +135,7 @@ export const SecurityProfilesTab = ({ config, serverName, onConfigChange }: Secu {config.securityProfiles.map((profile) => { - const isLastEnabled = - profile.enabled && config.securityProfiles.filter((p) => p.enabled).length <= 1 + const isLastEnabled = profile.enabled && config.securityProfiles.filter((p) => p.enabled).length <= 1 return ( diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx index de24c240a..1636f1659 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/user-modal.tsx @@ -176,8 +176,7 @@ export const UserModal = ({ // disabled silently — no error is shown until the user types into the // confirm field. const isSettingPassword = authType === 'password' && (!isEditing || password.length > 0) - const passwordsMismatchVisible = - isSettingPassword && confirmPassword.length > 0 && password !== confirmPassword + const passwordsMismatchVisible = isSettingPassword && confirmPassword.length > 0 && password !== confirmPassword const passwordsOk = !isSettingPassword || (confirmPassword.length > 0 && password === confirmPassword) const isValid = !usernameError && !passwordError && !certificateError && passwordsOk @@ -271,9 +270,7 @@ export const UserModal = ({ className={inputStyles} /> {usernameError && ( - - {usernameError} - + {usernameError} )}
      @@ -316,9 +313,7 @@ export const UserModal = ({
      {passwordError && ( - - {passwordError} - + {passwordError} )}
      @@ -333,9 +328,7 @@ export const UserModal = ({ className={inputStyles} /> {passwordsMismatchVisible && ( - - Passwords do not match - + Passwords do not match )}
    @@ -382,9 +375,7 @@ export const UserModal = ({ Select from trusted certificates configured in the Certificates tab {certificateError && ( - - {certificateError} - + {certificateError} )}
)} diff --git a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx index 2a45f6823..9db6c7291 100644 --- a/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx +++ b/src/frontend/components/_features/[workspace]/editor/server/opcua-server/components/users-tab.tsx @@ -144,9 +144,7 @@ export const UsersTab = ({ config, serverName, onConfigChange }: UsersTabProps) {user.type === 'password' ? 'Password' : 'Certificate'} - - {ROLE_LABELS[user.role]} - + {ROLE_LABELS[user.role]}