v1.10.0 (#471)
* fix select edit host but not update view (#438) * fix: Checksum issue with chocolatey * fix: Remove homebrew old stuff * Add Korean translation (#439) Co-authored-by: 송준우 <2484@coreit.co.kr> * feat: Automate flatpak * fix: Add imagemagik to electron builder to resolve build error * fix: Build error with runtime repo flag * fix: Flatpak runtime error and install freedesktop ver warning * fix: Flatpak runtime error and install freedesktop ver warning * feat: Re-add homebrew cask and move scripts to backend * fix: No sandbox flag issue * fix: Change name for electron macos cask output * fix: Sandbox error with Linux * fix: Remove comming soon for app stores in readme * Adding Comment at the end of the public_key on the host on deploy (#440) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * -Add New Interface for Credential DB -Add Credential Name as a comment into the server authorized_key file --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> * Sudo auto fill password (#441) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * Feature Sudo password auto-fill; * Fix locale json shema; --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> * Added Italian Language; (#445) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * Added Italian Language; --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> * Auto collapse snippet folders (#448) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * feat: Add collapsable snippets (customizable in user profile) * Translations (#447) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * Added Italian Language; * Fix translations; Removed duplicate keys, synchronised other languages using English as the source, translated added keys, fixed inaccurate translations. --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> * Remove PTY-level keepalive (#449) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * Remove PTY-level keepalive to prevent unwanted terminal output; use SSH-level keepalive instead --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> * feat: Seperate server stats and tunnel management (improved both UI's) then started initial docker implementation * fix: finalize adding docker to db * feat: Add docker management support (local squash) * Fix RBAC role system bugs and improve UX (#446) * Fix RBAC role system bugs and improve UX - Fix user list dropdown selection in host sharing - Fix role sharing permissions to include role-based access - Fix translation template interpolation for success messages - Standardize system roles to admin and user only - Auto-assign user role to new registrations - Remove blocking confirmation dialogs in modal contexts - Add missing i18n keys for common actions - Fix button type to prevent unintended form submissions * Enhance RBAC system with UI improvements and security fixes - Move role assignment to Users tab with per-user role management - Protect system roles (admin/user) from editing and manual assignment - Simplify permission system: remove Use level, keep View and Manage - Hide Update button and Sharing tab for view-only/shared hosts - Prevent users from sharing hosts with themselves - Unify table and modal styling across admin panels - Auto-assign system roles on user registration - Add permission metadata to host interface * Add empty state message for role assignment - Display helpful message when no custom roles available - Clarify that system roles are auto-assigned - Add noCustomRolesToAssign translation in English and Chinese * fix: Prevent credential sharing errors for shared hosts - Skip credential resolution for shared hosts with credential authentication to prevent decryption errors (credentials are encrypted per-user) - Add warning alert in sharing tab when host uses credential authentication - Inform users that shared users cannot connect to credential-based hosts - Add translations for credential sharing warning (EN/ZH) This prevents authentication failures when sharing hosts configured with credential authentication while maintaining security by keeping credentials isolated per user. * feat: Improve rbac UI and fixes some bugs --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com> * SOCKS5 support (#452) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * SOCKS5 support Adding single and chain socks5 proxy support * fix: cleanup files --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com> * Notes and Expiry fields add (#453) * Add termix.rb Cask file * Update Termix to version 1.9.0 with new checksum * Update README to remove 'coming soon' notes * Notes and Expiry add * fix: cleanup files --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com> * fix: ssh host types * fix: sudo incorrect styling and remove expiration date * feat: add sudo password and add diagonal bg's * fix: snippet running on enter key * fix: base64 decoding * fix: improve server stats / rbac * fix: wrap ssh host json export in hosts array * feat: auto trim host inputs, fix file manager jump hosts, dashboard prevent duplicates, file manager terminal not size updating, improve left sidebar sorting, hide/show tags, add apperance user profile tab, add new host manager tabs. * feat: improve terminal connection speed * fix: sqlite constriant errors and support non-root user (nginx perm issue) * feat: add beta syntax highlighing to terminal * feat: update imports and improve admin settings user management * chore: update translations * chore: update translations * feat: Complete light mode implementation with semantic theme system (#450) - Add comprehensive light/dark mode CSS variables with semantic naming - Implement theme-aware scrollbars using CSS variables - Add light mode backgrounds: --bg-base, --bg-elevated, --bg-surface, etc. - Add theme-aware borders: --border-base, --border-panel, --border-subtle - Add semantic text colors: --foreground-secondary, --foreground-subtle - Convert oklch colors to hex for better compatibility - Add theme awareness to CodeMirror editors - Update dark mode colors for consistency (background, sidebar, card, muted, input) - Add Tailwind color mappings for semantic classes Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> * fix: syntax errors * chore: updating/match themes and split admin settings * feat: add translation workflow and remove old translation.json * fix: translation workflow error * fix: translation workflow error * feat: improve translation system and update workflow * fix: wrong path for translations * fix: change translation to flat files * fix: gh rule error * chore: auto-translate to multiple languages (#458) * chore: improve organization and made a few styling changes in host manager * feat: improve terminal stability and split out the host manager * fix: add unnversiioned files * chore: migrate all to use the new theme system * fix: wrong animation line colors * fix: rbac implementation general issues (local squash) * fix: remove unneeded files * feat: add 10 new langs * chore: update gitnore * chore: auto-translate to multiple languages (#459) * fix: improve tunnel system * fix: properly split tabs, still need to fix up the host manager * chore: cleanup files (possible RC) * feat: add norwegian * chore: auto-translate to multiple languages (#461) * fix: small qol fixes and began readme update * fix: run cleanup script * feat: add docker docs button * feat: general bug fixes and readme updates * fix: translations * chore: auto-translate to multiple languages (#462) * fix: cleanup files * fix: test new translation issue and add better server-stats support * fix: fix translate error * chore: auto-translate to multiple languages (#463) * fix: fix translate mismatching text * chore: auto-translate to multiple languages (#465) * fix: fix translate mismatching text * fix: fix translate mismatching text * chore: auto-translate to multiple languages (#466) * fix: fix translate mismatching text * fix: fix translate mismatching text * fix: fix translate mismatching text * chore: auto-translate to multiple languages (#467) * fix: fix translate mismatching text * chore: auto-translate to multiple languages (#468) * feat: add to readme, a few qol changes, and improve server stats in general * chore: auto-translate to multiple languages (#469) * feat: turned disk uage into graph and fixed issue with termina console * fix: electron build error and hide icons when shared * chore: run clean * fix: general server stats issues, file manager decoding, ui qol * fix: add dashboard line breaks * fix: docker console error * fix: docker console not loading and mismatched stripped background for electron * fix: docker console not loading * chore: docker console not loading in docker * chore: translate readme to chinese * chore: match package lock to package json * chore: nginx config issue for dokcer console * chore: auto-translate to multiple languages (#470) --------- Co-authored-by: Tran Trung Kien <kientt13.7@gmail.com> Co-authored-by: junu <bigdwarf_@naver.com> Co-authored-by: 송준우 <2484@coreit.co.kr> Co-authored-by: SlimGary <trash.slim@gmail.com> Co-authored-by: Nunzio Marfè <nunzio.marfe@protonmail.com> Co-authored-by: Wesley Reid <starhound@lostsouls.org> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: Denis <38875137+Medvedinca@users.noreply.github.com> Co-authored-by: Peet McKinney <68706879+PeetMcK@users.noreply.github.com>
This commit was merged in pull request #471.
This commit is contained in:
@@ -11,7 +11,23 @@ import {
|
||||
} from "@/components/ui/tabs.tsx";
|
||||
import { Separator } from "@/components/ui/separator.tsx";
|
||||
import { Switch } from "@/components/ui/switch.tsx";
|
||||
import { User, Shield, AlertCircle } from "lucide-react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select.tsx";
|
||||
import {
|
||||
User,
|
||||
Shield,
|
||||
AlertCircle,
|
||||
Palette,
|
||||
Sun,
|
||||
Moon,
|
||||
Monitor,
|
||||
} from "lucide-react";
|
||||
import { useTheme } from "@/components/theme-provider";
|
||||
import { TOTPSetup } from "@/ui/desktop/user/TOTPSetup.tsx";
|
||||
import {
|
||||
getUserInfo,
|
||||
@@ -19,6 +35,8 @@ import {
|
||||
deleteAccount,
|
||||
logoutUser,
|
||||
isElectron,
|
||||
getUserRoles,
|
||||
type UserRole,
|
||||
} from "@/ui/main-axios.ts";
|
||||
import { PasswordReset } from "@/ui/desktop/user/PasswordReset.tsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -78,6 +96,7 @@ export function UserProfile({
|
||||
}: UserProfileProps) {
|
||||
const { t } = useTranslation();
|
||||
const { state: sidebarState } = useSidebar();
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [userInfo, setUserInfo] = useState<{
|
||||
username: string;
|
||||
is_admin: boolean;
|
||||
@@ -99,8 +118,21 @@ export function UserProfile({
|
||||
localStorage.getItem("fileColorCoding") !== "false",
|
||||
);
|
||||
const [commandAutocomplete, setCommandAutocomplete] = useState<boolean>(
|
||||
localStorage.getItem("commandAutocomplete") !== "false",
|
||||
localStorage.getItem("commandAutocomplete") === "true",
|
||||
);
|
||||
const [terminalSyntaxHighlighting, setTerminalSyntaxHighlighting] =
|
||||
useState<boolean>(
|
||||
() => localStorage.getItem("terminalSyntaxHighlighting") === "true",
|
||||
);
|
||||
const [defaultSnippetFoldersCollapsed, setDefaultSnippetFoldersCollapsed] =
|
||||
useState<boolean>(
|
||||
localStorage.getItem("defaultSnippetFoldersCollapsed") !== "false",
|
||||
);
|
||||
const [showHostTags, setShowHostTags] = useState<boolean>(() => {
|
||||
const saved = localStorage.getItem("showHostTags");
|
||||
return saved !== null ? saved === "true" : true;
|
||||
});
|
||||
const [userRoles, setUserRoles] = useState<UserRole[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserInfo();
|
||||
@@ -129,6 +161,14 @@ export function UserProfile({
|
||||
is_dual_auth: info.is_dual_auth || false,
|
||||
totp_enabled: info.totp_enabled || false,
|
||||
});
|
||||
|
||||
try {
|
||||
const rolesResponse = await getUserRoles(info.userId);
|
||||
setUserRoles(rolesResponse.roles || []);
|
||||
} catch (rolesErr) {
|
||||
console.error("Failed to fetch user roles:", rolesErr);
|
||||
setUserRoles([]);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const error = err as { response?: { data?: { error?: string } } };
|
||||
setError(error?.response?.data?.error || t("errors.loadFailed"));
|
||||
@@ -154,6 +194,24 @@ export function UserProfile({
|
||||
localStorage.setItem("commandAutocomplete", enabled.toString());
|
||||
};
|
||||
|
||||
const handleTerminalSyntaxHighlightingToggle = (enabled: boolean) => {
|
||||
setTerminalSyntaxHighlighting(enabled);
|
||||
localStorage.setItem("terminalSyntaxHighlighting", enabled.toString());
|
||||
window.dispatchEvent(new Event("terminalSyntaxHighlightingChanged"));
|
||||
};
|
||||
|
||||
const handleDefaultSnippetFoldersCollapsedToggle = (enabled: boolean) => {
|
||||
setDefaultSnippetFoldersCollapsed(enabled);
|
||||
localStorage.setItem("defaultSnippetFoldersCollapsed", enabled.toString());
|
||||
window.dispatchEvent(new Event("defaultSnippetFoldersCollapsedChanged"));
|
||||
};
|
||||
|
||||
const handleShowHostTagsToggle = (enabled: boolean) => {
|
||||
setShowHostTags(enabled);
|
||||
localStorage.setItem("showHostTags", enabled.toString());
|
||||
window.dispatchEvent(new Event("showHostTagsChanged"));
|
||||
};
|
||||
|
||||
const handleDeleteAccount = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setDeleteLoading(true);
|
||||
@@ -196,7 +254,7 @@ export function UserProfile({
|
||||
return (
|
||||
<div
|
||||
style={wrapperStyle}
|
||||
className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden"
|
||||
className="bg-canvas text-foreground rounded-lg border-2 border-edge overflow-hidden"
|
||||
>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="flex items-center justify-between px-3 pt-2 pb-2">
|
||||
@@ -204,7 +262,7 @@ export function UserProfile({
|
||||
</div>
|
||||
<Separator className="p-0.25 w-full" />
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="animate-pulse text-gray-300">
|
||||
<div className="animate-pulse text-foreground-secondary">
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,7 +275,7 @@ export function UserProfile({
|
||||
return (
|
||||
<div
|
||||
style={wrapperStyle}
|
||||
className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden"
|
||||
className="bg-canvas text-foreground rounded-lg border-2 border-edge overflow-hidden"
|
||||
>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="flex items-center justify-between px-3 pt-2 pb-2">
|
||||
@@ -247,7 +305,7 @@ export function UserProfile({
|
||||
<>
|
||||
<div
|
||||
style={wrapperStyle}
|
||||
className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden"
|
||||
className="bg-canvas text-foreground rounded-lg border-2 border-edge overflow-hidden"
|
||||
>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="flex items-center justify-between px-3 pt-2 pb-2">
|
||||
@@ -255,20 +313,27 @@ export function UserProfile({
|
||||
</div>
|
||||
<Separator className="p-0.25 w-full" />
|
||||
|
||||
<div className="px-6 py-4 overflow-auto flex-1">
|
||||
<div className="px-6 py-4 overflow-auto thin-scrollbar flex-1">
|
||||
<Tabs defaultValue="profile" className="w-full">
|
||||
<TabsList className="mb-4 bg-dark-bg border-2 border-dark-border">
|
||||
<TabsList className="mb-4 bg-elevated border-2 border-edge">
|
||||
<TabsTrigger
|
||||
value="profile"
|
||||
className="flex items-center gap-2 data-[state=active]:bg-dark-bg-button"
|
||||
className="flex items-center gap-2 bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge"
|
||||
>
|
||||
<User className="w-4 h-4" />
|
||||
{t("nav.userProfile")}
|
||||
{t("profile.account")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="appearance"
|
||||
className="flex items-center gap-2 data-[state=active]:bg-button"
|
||||
>
|
||||
<Palette className="w-4 h-4" />
|
||||
{t("profile.appearance")}
|
||||
</TabsTrigger>
|
||||
{(!userInfo.is_oidc || userInfo.is_dual_auth) && (
|
||||
<TabsTrigger
|
||||
value="security"
|
||||
className="flex items-center gap-2 data-[state=active]:bg-dark-bg-button"
|
||||
className="flex items-center gap-2 bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge"
|
||||
>
|
||||
<Shield className="w-4 h-4" />
|
||||
{t("profile.security")}
|
||||
@@ -277,34 +342,49 @@ export function UserProfile({
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="profile" className="space-y-4">
|
||||
<div className="rounded-lg border-2 border-dark-border bg-dark-bg-darker p-4">
|
||||
<div className="rounded-lg border-2 border-edge bg-elevated p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{t("profile.accountInfo")}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("common.username")}
|
||||
</Label>
|
||||
<p className="text-lg font-medium mt-1 text-white">
|
||||
<p className="text-lg font-medium mt-1 text-foreground">
|
||||
{userInfo.username}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.role")}
|
||||
</Label>
|
||||
<p className="text-lg font-medium mt-1 text-white">
|
||||
{userInfo.is_admin
|
||||
? t("interface.administrator")
|
||||
: t("interface.user")}
|
||||
</p>
|
||||
<div className="mt-1">
|
||||
{userRoles.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{userRoles.map((role) => (
|
||||
<span
|
||||
key={role.roleId}
|
||||
className="inline-flex items-center px-2.5 py-1 rounded-md text-sm font-medium bg-muted/50 text-foreground border border-border"
|
||||
>
|
||||
{t(role.roleDisplayName)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-lg font-medium text-foreground">
|
||||
{userInfo.is_admin
|
||||
? t("interface.administrator")
|
||||
: t("interface.user")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.authMethod")}
|
||||
</Label>
|
||||
<p className="text-lg font-medium mt-1 text-white">
|
||||
<p className="text-lg font-medium mt-1 text-foreground">
|
||||
{userInfo.is_dual_auth
|
||||
? t("profile.externalAndLocal")
|
||||
: userInfo.is_oidc
|
||||
@@ -313,12 +393,12 @@ export function UserProfile({
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.twoFactorAuth")}
|
||||
</Label>
|
||||
<p className="text-lg font-medium mt-1">
|
||||
{userInfo.is_oidc && !userInfo.is_dual_auth ? (
|
||||
<span className="text-gray-400">
|
||||
<span className="text-muted-foreground">
|
||||
{t("auth.lockedOidcAuth")}
|
||||
</span>
|
||||
) : userInfo.totp_enabled ? (
|
||||
@@ -327,43 +407,117 @@ export function UserProfile({
|
||||
{t("common.enabled")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-400">
|
||||
<span className="text-muted-foreground">
|
||||
{t("common.disabled")}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("common.version")}
|
||||
</Label>
|
||||
<p className="text-lg font-medium mt-1 text-white">
|
||||
<p className="text-lg font-medium mt-1 text-foreground">
|
||||
{versionInfo?.version || t("common.loading")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-dark-border">
|
||||
<div className="mt-6 pt-6 border-t border-edge">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-red-400">
|
||||
{t("leftSidebar.deleteAccount")}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("leftSidebar.deleteAccountWarningShort")}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteAccountOpen(true)}
|
||||
>
|
||||
{t("leftSidebar.deleteAccount")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="appearance" className="space-y-4">
|
||||
<div className="rounded-lg border-2 border-edge bg-elevated p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{t("profile.languageLocalization")}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("common.language")}
|
||||
</Label>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("profile.selectPreferredLanguage")}
|
||||
</p>
|
||||
</div>
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-dark-border">
|
||||
<div className="rounded-lg border-2 border-edge bg-elevated p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{t("profile.appearance")}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.theme")}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("profile.appearanceDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<Select value={theme} onValueChange={setTheme}>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="light">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sun className="w-4 h-4" />
|
||||
{t("profile.themeLight")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="dark">
|
||||
<div className="flex items-center gap-2">
|
||||
<Moon className="w-4 h-4" />
|
||||
{t("profile.themeDark")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="system">
|
||||
<div className="flex items-center gap-2">
|
||||
<Monitor className="w-4 h-4" />
|
||||
{t("profile.themeSystem")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border-2 border-edge bg-elevated p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{t("profile.fileManagerSettings")}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.fileColorCoding")}
|
||||
</Label>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("profile.fileColorCodingDesc")}
|
||||
</p>
|
||||
</div>
|
||||
@@ -373,14 +527,19 @@ export function UserProfile({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-dark-border">
|
||||
<div className="rounded-lg border-2 border-edge bg-elevated p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{t("profile.terminalSettings")}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-gray-300">
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.commandAutocomplete")}
|
||||
</Label>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("profile.commandAutocompleteDesc")}
|
||||
</p>
|
||||
</div>
|
||||
@@ -389,27 +548,68 @@ export function UserProfile({
|
||||
onCheckedChange={handleCommandAutocompleteToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-dark-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-red-400">
|
||||
{t("leftSidebar.deleteAccount")}
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.terminalSyntaxHighlighting")}{" "}
|
||||
<span className="text-xs text-yellow-500 font-semibold">
|
||||
(BETA)
|
||||
</span>
|
||||
</Label>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
{t(
|
||||
"leftSidebar.deleteAccountWarningShort",
|
||||
"This action is not reversible and will permanently delete your account.",
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("profile.terminalSyntaxHighlightingDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteAccountOpen(true)}
|
||||
>
|
||||
{t("leftSidebar.deleteAccount")}
|
||||
</Button>
|
||||
<Switch
|
||||
checked={terminalSyntaxHighlighting}
|
||||
onCheckedChange={handleTerminalSyntaxHighlightingToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border-2 border-edge bg-elevated p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{t("profile.hostSidebarSettings")}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.showHostTags")}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("profile.showHostTagsDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={showHostTags}
|
||||
onCheckedChange={handleShowHostTagsToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border-2 border-edge bg-elevated p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{t("profile.snippetsSettings")}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-foreground-secondary">
|
||||
{t("profile.defaultSnippetFoldersCollapsed")}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t("profile.defaultSnippetFoldersCollapsedDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={defaultSnippetFoldersCollapsed}
|
||||
onCheckedChange={
|
||||
handleDefaultSnippetFoldersCollapsedToggle
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -438,15 +638,15 @@ export function UserProfile({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-[400px] h-full bg-dark-bg border-r-2 border-dark-border flex flex-col shadow-2xl relative isolate z-[9999999]"
|
||||
className="w-[400px] h-full bg-canvas border-r-2 border-edge flex flex-col shadow-2xl relative isolate z-[9999999]"
|
||||
style={{
|
||||
boxShadow: "4px 0 20px rgba(0, 0, 0, 0.5)",
|
||||
transform: "translateZ(0)",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4 border-b border-dark-border">
|
||||
<h2 className="text-lg font-semibold text-white">
|
||||
<div className="flex items-center justify-between p-4 border-b border-edge">
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
{t("leftSidebar.deleteAccount")}
|
||||
</h2>
|
||||
<Button
|
||||
@@ -457,16 +657,16 @@ export function UserProfile({
|
||||
setDeletePassword("");
|
||||
setDeleteError(null);
|
||||
}}
|
||||
className="h-8 w-8 p-0 hover:bg-red-500 hover:text-white transition-colors flex items-center justify-center"
|
||||
className="h-8 w-8 p-0 hover:bg-red-500 hover:text-foreground transition-colors flex items-center justify-center"
|
||||
title={t("leftSidebar.closeDeleteAccount")}
|
||||
>
|
||||
<span className="text-lg font-bold leading-none">×</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<div className="flex-1 overflow-y-auto p-4 thin-scrollbar">
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-gray-300">
|
||||
<div className="text-sm text-foreground-secondary">
|
||||
{t("leftSidebar.deleteAccountWarning")}
|
||||
<Alert variant="destructive" className="mb-5 mt-5">
|
||||
<AlertTitle>{t("common.warning")}</AlertTitle>
|
||||
|
||||
Reference in New Issue
Block a user