diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index e0617cf5..d82d7164 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -165,8 +165,7 @@ "keyTypeEcdsaP521": "ECDSA P-521 (SSH)", "keyTypeDsa": "DSA (SSH)", "keyTypeRsaSha256": "RSA-SHA2-256", - "keyTypeRsaSha512": "RSA-SHA2-512" - }, + "keyTypeRsaSha512": "RSA-SHA2-512", "keyPairGeneratedSuccessfully": "{{keyType}} key pair generated successfully", "failedToGenerateKeyPair": "Failed to generate key pair", "generateKeyPairNote": "Generate a new SSH key pair directly. This will replace any existing keys in the form.", @@ -286,7 +285,30 @@ "deleteTooltip": "Delete command", "tabHint": "Use Tab in Terminal to autocomplete from command history", "authRequiredRefresh": "Authentication required. Please refresh the page.", - "dataAccessLockedReauth": "Data access locked. Please re-authenticate." + "dataAccessLockedReauth": "Data access locked. Please re-authenticate.", + "loading": "Loading command history...", + "error": "Error Loading History" + }, + "splitScreen": { + "title": "Split Screen", + "none": "None", + "twoSplit": "2-Way", + "threeSplit": "3-Way", + "fourSplit": "4-Way", + "availableTabs": "Available Tabs", + "dragTabsHint": "Drag tabs to the layout cells below to assign them", + "layout": "Split Screen Layout", + "dropHere": "Drop tab here", + "apply": "Apply Split", + "clear": "Clear Split", + "selectMode": "Select a split screen mode", + "helpText": "Choose how many tabs you want to view at once", + "success": "Split screen applied successfully", + "cleared": "Split screen cleared", + "error": { + "noAssignments": "Please assign at least one tab to the layout", + "fillAllSlots": "Please fill all {{count}} slots before applying" + } }, "homepage": { "loggedInTitle": "Logged in!", @@ -1578,7 +1600,7 @@ "connecting": "Connecting...", "disconnecting": "Disconnecting...", "unknownTunnelStatus": "Unknown", - "statusUnknown": "Unknown" + "statusUnknown": "Unknown", "error": "Error", "failed": "Failed", "retrying": "Retrying", @@ -1593,7 +1615,7 @@ "attempt": "Attempt {{current}} of {{max}}", "nextRetryIn": "Next retry in {{seconds}} seconds", "checkDockerLogs": "Check your Docker logs for the error reason, join the", - "orCreate": "or create a ",, + "orCreate": "or create a ", "noTunnelConnections": "No tunnel connections configured", "tunnelConnections": "Tunnel Connections", "addTunnel": "Add Tunnel", diff --git a/src/ui/desktop/admin/AdminSettings.tsx b/src/ui/desktop/admin/AdminSettings.tsx index 1bd43e0f..79e13442 100644 --- a/src/ui/desktop/admin/AdminSettings.tsx +++ b/src/ui/desktop/admin/AdminSettings.tsx @@ -768,23 +768,38 @@ export function AdminSettings({ {t("admin.general")} - + OIDC - + {t("admin.users")} - + Sessions - + {t("rbac.roles.label")} - + {t("admin.databaseSecurity")} @@ -795,27 +810,27 @@ export function AdminSettings({

{t("admin.userRegistration")}

- - + + @@ -1189,7 +1204,9 @@ export function AdminSettings({
-

{t("admin.sessionManagement")}

+

+ {t("admin.sessionManagement")} +

@@ -1314,7 +1333,9 @@ export function AdminSettings({ - +
+ +
@@ -1457,10 +1478,11 @@ export function AdminSettings({ > - - - {t("admin.linkOidcToPasswordAccount")} - + + + {t("admin.linkOidcToPasswordAccount")} + {" "} + {t("admin.linkOidcToPasswordAccountDescription", { username: linkOidcUser?.username, })} diff --git a/src/ui/desktop/admin/RoleManagement.tsx b/src/ui/desktop/admin/RoleManagement.tsx index 27d486c1..681b16f1 100644 --- a/src/ui/desktop/admin/RoleManagement.tsx +++ b/src/ui/desktop/admin/RoleManagement.tsx @@ -19,23 +19,8 @@ import { import { Input } from "@/components/ui/input.tsx"; import { Label } from "@/components/ui/label.tsx"; import { Textarea } from "@/components/ui/textarea.tsx"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select.tsx"; import { Badge } from "@/components/ui/badge.tsx"; -import { - Shield, - Plus, - Edit, - Trash2, - Users, - Check, - ChevronsUpDown, -} from "lucide-react"; +import { Shield, Plus, Edit, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { useConfirmation } from "@/hooks/use-confirmation.ts"; @@ -44,39 +29,14 @@ import { createRole, updateRole, deleteRole, - getUserList, - getUserRoles, - assignRoleToUser, - removeRoleFromUser, type Role, - type UserRole, } from "@/ui/main-axios.ts"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command.tsx"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover.tsx"; -import { cn } from "@/lib/utils"; - -interface User { - id: string; - username: string; - is_admin: boolean; -} export function RoleManagement(): React.ReactElement { const { t } = useTranslation(); const { confirmWithToast } = useConfirmation(); const [roles, setRoles] = React.useState([]); - const [users, setUsers] = React.useState([]); const [loading, setLoading] = React.useState(false); // Create/Edit Role Dialog @@ -86,18 +46,6 @@ export function RoleManagement(): React.ReactElement { const [roleDisplayName, setRoleDisplayName] = React.useState(""); const [roleDescription, setRoleDescription] = React.useState(""); - // Assign Role Dialog - const [assignDialogOpen, setAssignDialogOpen] = React.useState(false); - const [selectedUserId, setSelectedUserId] = React.useState(""); - const [selectedRoleId, setSelectedRoleId] = React.useState( - null, - ); - const [userRoles, setUserRoles] = React.useState([]); - - // Combobox states - const [userComboOpen, setUserComboOpen] = React.useState(false); - const [roleComboOpen, setRoleComboOpen] = React.useState(false); - // Load roles const loadRoles = React.useCallback(async () => { setLoading(true); @@ -113,27 +61,9 @@ export function RoleManagement(): React.ReactElement { } }, [t]); - // Load users - const loadUsers = React.useCallback(async () => { - try { - const response = await getUserList(); - // Map UserInfo to User format - const mappedUsers = (response.users || []).map((user) => ({ - id: user.id, - username: user.username, - is_admin: user.is_admin, - })); - setUsers(mappedUsers); - } catch (error) { - console.error("Failed to load users:", error); - setUsers([]); - } - }, []); - React.useEffect(() => { loadRoles(); - loadUsers(); - }, [loadRoles, loadUsers]); + }, [loadRoles]); // Create role const handleCreateRole = () => { @@ -212,70 +142,6 @@ export function RoleManagement(): React.ReactElement { } }; - // Open assign dialog - const handleOpenAssignDialog = async () => { - setSelectedUserId(""); - setSelectedRoleId(null); - setUserRoles([]); - setAssignDialogOpen(true); - }; - - // Load user roles when user is selected - const handleUserSelect = async (userId: string) => { - setSelectedUserId(userId); - setUserRoles([]); - - if (!userId) return; - - try { - const response = await getUserRoles(userId); - setUserRoles(response.roles || []); - } catch (error) { - console.error("Failed to load user roles:", error); - setUserRoles([]); - } - }; - - // Assign role to user - const handleAssignRole = async () => { - if (!selectedUserId || !selectedRoleId) { - toast.error(t("rbac.selectUserAndRole")); - return; - } - - try { - await assignRoleToUser(selectedUserId, selectedRoleId); - const selectedUser = users.find((u) => u.id === selectedUserId); - toast.success( - t("rbac.roleAssignedSuccessfully", { - username: selectedUser?.username || selectedUserId, - }), - ); - setSelectedRoleId(null); - handleUserSelect(selectedUserId); - } catch (error) { - toast.error(t("rbac.failedToAssignRole")); - } - }; - - // Remove role from user - const handleRemoveUserRole = async (roleId: number) => { - if (!selectedUserId) return; - - try { - await removeRoleFromUser(selectedUserId, roleId); - const selectedUser = users.find((u) => u.id === selectedUserId); - toast.success( - t("rbac.roleRemovedSuccessfully", { - username: selectedUser?.username || selectedUserId, - }), - ); - handleUserSelect(selectedUserId); - } catch (error) { - toast.error(t("rbac.failedToRemoveRole")); - } - }; - return (
{/* Roles Section */} @@ -366,20 +232,6 @@ export function RoleManagement(): React.ReactElement {
- {/* User-Role Assignment Section */} -
-
-

- - {t("rbac.userRoleAssignment")} -

- -
-
- {/* Create/Edit Role Dialog */} @@ -443,208 +295,6 @@ export function RoleManagement(): React.ReactElement { - - {/* Assign Role Dialog */} - - - - {t("rbac.assignRoles")} - - {t("rbac.assignRolesDescription")} - - - -
- {/* User Selection */} -
- - - - - - - - - {t("rbac.noUserFound")} - - {users.map((user) => ( - { - handleUserSelect(user.id); - setUserComboOpen(false); - }} - > - - {user.username} - {user.is_admin ? " (Admin)" : ""} - - ))} - - - - -
- - {/* Current User Roles */} - {selectedUserId && ( -
- - {userRoles.length === 0 ? ( -

- {t("rbac.noRolesAssigned")} -

- ) : ( -
- {userRoles.map((userRole, index) => ( -
-
-

- {t(userRole.roleDisplayName)} -

- {userRole.roleDisplayName && ( -

- {userRole.roleName} -

- )} -
-
- {userRole.isSystem ? ( - - {t("rbac.systemRole")} - - ) : ( - - )} -
-
- ))} -
- )} -
- )} - - {/* Assign New Role */} - {selectedUserId && ( -
- -
- - - - - - - - {t("rbac.noRoleFound")} - - {roles - .filter( - (role) => - !role.isSystem && - !userRoles.some((ur) => ur.roleId === role.id), - ) - .map((role) => ( - { - setSelectedRoleId(role.id); - setRoleComboOpen(false); - }} - > - - {t(role.displayName)} - {role.isSystem - ? ` (${t("rbac.systemRole")})` - : ""} - - ))} - - - - - -
-
- )} -
- - - - -
-
); } diff --git a/src/ui/desktop/apps/server-stats/widgets/LoginStatsWidget.tsx b/src/ui/desktop/apps/server-stats/widgets/LoginStatsWidget.tsx index 0f4ccd6e..65c747e7 100644 --- a/src/ui/desktop/apps/server-stats/widgets/LoginStatsWidget.tsx +++ b/src/ui/desktop/apps/server-stats/widgets/LoginStatsWidget.tsx @@ -36,7 +36,6 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) { return (
-

@@ -78,15 +77,10 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {

) : (
- {recentLogins.slice(0, 5).map((login) => ( + {recentLogins.slice(0, 5).map((login, idx) => (
diff --git a/src/ui/desktop/apps/server-stats/widgets/ProcessesWidget.tsx b/src/ui/desktop/apps/server-stats/widgets/ProcessesWidget.tsx index 9ae82ca6..3e95decc 100644 --- a/src/ui/desktop/apps/server-stats/widgets/ProcessesWidget.tsx +++ b/src/ui/desktop/apps/server-stats/widgets/ProcessesWidget.tsx @@ -29,7 +29,6 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) { return (
-

@@ -60,15 +59,10 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {

) : (
- {topProcesses.map((proc) => ( + {topProcesses.map((proc, index) => (
@@ -82,7 +76,9 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
{proc.command}
-
User: {proc.user}
+
+ User: {proc.user} +
))}
diff --git a/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx b/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx index 2fa4eba7..96ef9cec 100644 --- a/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx +++ b/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx @@ -813,9 +813,7 @@ export function SSHToolsSidebar({ const targetFolder = targetSnippet.folder || ""; if (sourceFolder !== targetFolder) { - toast.error( - t("snippets.reorderSameFolder"), - ); + toast.error(t("snippets.reorderSameFolder")); setDraggedSnippet(null); setDragOverFolder(null); return; @@ -850,14 +848,10 @@ export function SSHToolsSidebar({ try { await reorderSnippets(updates); - toast.success( - t("snippets.reorderSuccess"), - ); + toast.success(t("snippets.reorderSuccess")); fetchSnippets(); } catch { - toast.error( - t("snippets.reorderFailed"), - ); + toast.error(t("snippets.reorderFailed")); } setDraggedSnippet(null); @@ -895,6 +889,7 @@ export function SSHToolsSidebar({ confirmWithToast( t("snippets.deleteFolderConfirm", { name: folderName, + }), async () => { try { await deleteSnippetFolder(folderName); @@ -1013,9 +1008,7 @@ export function SSHToolsSidebar({ } if (splitAssignments.size === 0) { - toast.error( - t("splitScreen.error.noAssignments"), - ); + toast.error(t("splitScreen.error.noAssignments")); return; } @@ -1051,9 +1044,7 @@ export function SSHToolsSidebar({ setCurrentTab(orderedTabIds[0]); } - toast.success( - t("splitScreen.success"), - ); + toast.success(t("splitScreen.success")); }; const handleClearSplit = () => { @@ -1065,9 +1056,7 @@ export function SSHToolsSidebar({ setSplitAssignments(new Map()); setPreviewKey((prev) => prev + 1); - toast.success( - t("splitScreen.cleared"), - ); + toast.success(t("splitScreen.cleared")); }; const handleResetToSingle = () => { @@ -1085,13 +1074,9 @@ export function SSHToolsSidebar({ try { await deleteCommandFromHistory(activeTerminalHostId, command); setCommandHistory((prev) => prev.filter((c) => c !== command)); - toast.success( - t("commandHistory.deleteSuccess"), - ); + toast.success(t("commandHistory.deleteSuccess")); } catch { - toast.error( - t("commandHistory.deleteFailed"), - ); + toast.error(t("commandHistory.deleteFailed")); } } }; @@ -1606,7 +1591,8 @@ export function SSHToolsSidebar({

- {t("commandHistory.noTerminal")}

+ {t("commandHistory.noTerminal")}{" "} +

{t("commandHistory.noTerminalHint")}

@@ -1615,7 +1601,8 @@ export function SSHToolsSidebar({

- {t("commandHistory.loading")}

+ {t("commandHistory.loading")}{" "} +

) : filteredCommands.length === 0 ? (
@@ -1699,11 +1686,14 @@ export function SSHToolsSidebar({ {t("splitScreen.none")} - {t("splitScreen.twoSplit")} + {t("splitScreen.twoSplit")}{" "} + - {t("splitScreen.threeSplit")} + {t("splitScreen.threeSplit")}{" "} + - {t("splitScreen.fourSplit")} + {t("splitScreen.fourSplit")}{" "} + @@ -1759,7 +1749,7 @@ export function SSHToolsSidebar({ {t("splitScreen.layout")}
- + @@ -2051,7 +2039,7 @@ export function SSHToolsSidebar({

{editingFolder ? t("snippets.editFolder") - : t("snippets.createFolder") + : t("snippets.createFolder")}

{editingFolder @@ -2155,8 +2143,7 @@ export function SSHToolsSidebar({ ); })()} - {folderFormData.name || - t("snippets.folderName")} + {folderFormData.name || t("snippets.folderName")}

@@ -2175,7 +2162,7 @@ export function SSHToolsSidebar({
diff --git a/src/ui/desktop/navigation/LeftSidebar.tsx b/src/ui/desktop/navigation/LeftSidebar.tsx index 485ee332..735a50e7 100644 --- a/src/ui/desktop/navigation/LeftSidebar.tsx +++ b/src/ui/desktop/navigation/LeftSidebar.tsx @@ -120,7 +120,7 @@ export function LeftSidebar({ setCurrentTab(sshManagerTab.id); return; } - const id = addTab({ type: "ssh_manager", title: t('nav.hostManager') }); + const id = addTab({ type: "ssh_manager", title: t("nav.hostManager") }); setCurrentTab(id); }; const adminTab = tabList.find((t) => t.type === "admin"); @@ -481,7 +481,7 @@ export function LeftSidebar({ - {t('common.appName')} + {t("common.appName")}