chore: rename app from ProxmoxDesktop to Clustri
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# ProxmoxDesktop
|
# Clustri
|
||||||
|
|
||||||
A cross-platform desktop client for managing Proxmox VE servers and clusters. The backend is Rust on Tauri 2; the UI is React 19 with TypeScript, Vite, and Tailwind CSS v4.
|
A cross-platform desktop client for managing Proxmox VE servers and clusters. The backend is Rust on Tauri 2; the UI is React 19 with TypeScript, Vite, and Tailwind CSS v4.
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ A cross-platform desktop client for managing Proxmox VE servers and clusters. Th
|
|||||||
- Manage multiple simultaneous connections to servers and clusters.
|
- Manage multiple simultaneous connections to servers and clusters.
|
||||||
- Authenticate with an API token or with a username and password. Password mode uses ticket-based auth (PVEAuthCookie plus CSRF token).
|
- Authenticate with an API token or with a username and password. Password mode uses ticket-based auth (PVEAuthCookie plus CSRF token).
|
||||||
- Credentials live in the OS keyring. API tokens never touch disk.
|
- Credentials live in the OS keyring. API tokens never touch disk.
|
||||||
- Connections persist to `{app-config-dir}/proxmoxdesktop/connections.json`. The last active connection reconnects on launch.
|
- Connections persist to `{app-config-dir}/clustri/connections.json`. The last active connection reconnects on launch.
|
||||||
|
|
||||||
### Cluster support
|
### Cluster support
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ source $HOME/.cargo/env
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
cd ProxmoxDesktop
|
cd clustri
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ When adding a connection you need:
|
|||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
ProxmoxDesktop/
|
clustri/
|
||||||
├── src/ # React frontend
|
├── src/ # React frontend
|
||||||
│ ├── components/
|
│ ├── components/
|
||||||
│ │ ├── ui/ # base components (buttons, dialogs, toasts)
|
│ │ ├── ui/ # base components (buttons, dialogs, toasts)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>ProxmoxDesktop</title>
|
<title>Clustri</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "proxmoxdesktop",
|
"name": "clustri",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "proxmoxdesktop",
|
"name": "clustri",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/geist": "^5.3.0",
|
"@fontsource-variable/geist": "^5.3.0",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "proxmoxdesktop",
|
"name": "clustri",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Generated
+29
-29
@@ -411,6 +411,35 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clustri"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"hex",
|
||||||
|
"httpmock",
|
||||||
|
"keyring",
|
||||||
|
"percent-encoding",
|
||||||
|
"rcgen",
|
||||||
|
"reqwest 0.12.28",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"tauri",
|
||||||
|
"tauri-build",
|
||||||
|
"tauri-plugin-shell",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror 2.0.19",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tokio-tungstenite",
|
||||||
|
"url",
|
||||||
|
"uuid",
|
||||||
|
"x509-cert",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.58"
|
version = "0.1.58"
|
||||||
@@ -2704,35 +2733,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proxmox-desktop"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"futures-util",
|
|
||||||
"hex",
|
|
||||||
"httpmock",
|
|
||||||
"keyring",
|
|
||||||
"percent-encoding",
|
|
||||||
"rcgen",
|
|
||||||
"reqwest 0.12.28",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pemfile",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tauri",
|
|
||||||
"tauri-build",
|
|
||||||
"tauri-plugin-shell",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror 2.0.19",
|
|
||||||
"tokio",
|
|
||||||
"tokio-rustls",
|
|
||||||
"tokio-tungstenite",
|
|
||||||
"url",
|
|
||||||
"uuid",
|
|
||||||
"x509-cert",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "proxmox-desktop"
|
name = "clustri"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A cross-platform desktop application for managing Proxmox VE servers"
|
description = "A cross-platform desktop application for managing Proxmox VE servers"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Default capabilities for ProxmoxDesktop",
|
"description": "Default capabilities for Clustri",
|
||||||
"windows": ["main"],
|
"windows": ["main"],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"default":{"identifier":"default","description":"Default capabilities for ProxmoxDesktop","local":true,"windows":["main"],"permissions":["core:default","core:event:default","core:event:allow-listen","core:event:allow-emit","core:event:allow-emit-to","core:window:default","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-focus","core:window:allow-is-visible","shell:allow-execute","shell:default"]}}
|
{"default":{"identifier":"default","description":"Default capabilities for Clustri","local":true,"windows":["main"],"permissions":["core:default","core:event:default","core:event:allow-listen","core:event:allow-emit","core:event:allow-emit-to","core:window:default","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-focus","core:window:allow-is-visible","shell:allow-execute","shell:default"]}}
|
||||||
@@ -370,7 +370,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn keyring_service() -> &'static str {
|
fn keyring_service() -> &'static str {
|
||||||
"proxmox-desktop"
|
"clustri"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyring_entry(connection_id: &str, field: &str) -> crate::Result<keyring::Entry> {
|
fn keyring_entry(connection_id: &str, field: &str) -> crate::Result<keyring::Entry> {
|
||||||
|
|||||||
@@ -129,11 +129,11 @@ struct AppState {
|
|||||||
|
|
||||||
/// Returns the path of the persisted connections file.
|
/// Returns the path of the persisted connections file.
|
||||||
///
|
///
|
||||||
/// Lives at `{app_config_dir}/proxmoxdesktop/connections.json`; the parent
|
/// Lives at `{app_config_dir}/clustri/connections.json`; the parent
|
||||||
/// directory is created lazily when the file is first written.
|
/// directory is created lazily when the file is first written.
|
||||||
fn connections_file(app: &tauri::AppHandle) -> Result<PathBuf> {
|
fn connections_file(app: &tauri::AppHandle) -> Result<PathBuf> {
|
||||||
let dir = app.path().app_config_dir()?;
|
let dir = app.path().app_config_dir()?;
|
||||||
Ok(dir.join("proxmoxdesktop").join("connections.json"))
|
Ok(dir.join("clustri").join("connections.json"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -939,7 +939,7 @@ pub fn run() {
|
|||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let _tray = TrayIconBuilder::with_id("main-tray")
|
let _tray = TrayIconBuilder::with_id("main-tray")
|
||||||
.tooltip("ProxmoxDesktop")
|
.tooltip("Clustri")
|
||||||
.icon(app.default_window_icon().cloned().expect("no default icon"))
|
.icon(app.default_window_icon().cloned().expect("no default icon"))
|
||||||
.menu(&menu)
|
.menu(&menu)
|
||||||
.on_menu_event(move |app, event| match event.id.as_ref() {
|
.on_menu_event(move |app, event| match event.id.as_ref() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
proxmox_desktop::run()
|
clustri::run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "ProxmoxDesktop",
|
"productName": "Clustri",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"identifier": "com.proxmoxdesktop.app",
|
"identifier": "dev.clustri.app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:1420",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "ProxmoxDesktop",
|
"title": "Clustri",
|
||||||
"width": 1200,
|
"width": 1200,
|
||||||
"height": 800,
|
"height": 800,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//! error mapping, query parameters, and URL construction.
|
//! error mapping, query parameters, and URL construction.
|
||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use proxmox_desktop::{api_request, AuthContext, AuthMode, Error};
|
use clustri::{api_request, AuthContext, AuthMode, Error};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest::Method as RMethod;
|
use reqwest::Method as RMethod;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
//! called directly on an added connection without `connect()`.
|
//! called directly on an added connection without `connect()`.
|
||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use proxmox_desktop::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
use clustri::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
||||||
|
|
||||||
/// Builds a `ConnectionManager` with a single token-mode connection whose
|
/// Builds a `ConnectionManager` with a single token-mode connection whose
|
||||||
/// primary endpoint points at the mock server. `node` pins the storage node.
|
/// primary endpoint points at the mock server. `node` pins the storage node.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
//! directly on an added connection without `connect()`.
|
//! directly on an added connection without `connect()`.
|
||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use proxmox_desktop::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
use clustri::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
||||||
|
|
||||||
/// Builds a `ConnectionManager` with a single token-mode connection whose
|
/// Builds a `ConnectionManager` with a single token-mode connection whose
|
||||||
/// primary endpoint points at `url`.
|
/// primary endpoint points at `url`.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
//! `body_includes` assertions use the encoded form (e.g. `:` becomes `%3A`).
|
//! `body_includes` assertions use the encoded form (e.g. `:` becomes `%3A`).
|
||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use proxmox_desktop::{
|
use clustri::{
|
||||||
AddDiskConfig, AddNICConfig, ConnectionConfig, ConnectionManager, EditNICConfig,
|
AddDiskConfig, AddNICConfig, ConnectionConfig, ConnectionManager, EditNICConfig,
|
||||||
EndpointConfig, Error,
|
EndpointConfig, Error,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! directly on an added connection without `connect()`.
|
//! directly on an added connection without `connect()`.
|
||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use proxmox_desktop::{
|
use clustri::{
|
||||||
BackupJobConfig, ConnectionConfig, ConnectionManager, CreateSnapshotConfig, EndpointConfig,
|
BackupJobConfig, ConnectionConfig, ConnectionManager, CreateSnapshotConfig, EndpointConfig,
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use httpmock::Mock;
|
use httpmock::Mock;
|
||||||
use proxmox_desktop::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
use clustri::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
||||||
|
|
||||||
const TOKEN: &str = "root@pam!cluster-token";
|
const TOKEN: &str = "root@pam!cluster-token";
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use httpmock::Mock;
|
use httpmock::Mock;
|
||||||
use proxmox_desktop::{derive_node_url, ConnectionConfig, ConnectionManager, EndpointConfig};
|
use clustri::{derive_node_url, ConnectionConfig, ConnectionManager, EndpointConfig};
|
||||||
|
|
||||||
const TOKEN: &str = "root@pam!discovery-token";
|
const TOKEN: &str = "root@pam!discovery-token";
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use httpmock::Mock;
|
use httpmock::Mock;
|
||||||
use proxmox_desktop::{
|
use clustri::{
|
||||||
api_request, AuthContext, AuthMode, ConnectionConfig, ConnectionManager, EndpointConfig, Error,
|
api_request, AuthContext, AuthMode, ConnectionConfig, ConnectionManager, EndpointConfig, Error,
|
||||||
};
|
};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! request layer falls back to the OS keyring.
|
//! request layer falls back to the OS keyring.
|
||||||
|
|
||||||
use httpmock::prelude::*;
|
use httpmock::prelude::*;
|
||||||
use proxmox_desktop::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
use clustri::{ConnectionConfig, ConnectionManager, EndpointConfig, Error};
|
||||||
|
|
||||||
/// Builds a token-mode connection config with a real token set.
|
/// Builds a token-mode connection config with a real token set.
|
||||||
fn token_config(id: &str, url: &str, token: &str, accept_untrusted: bool) -> ConnectionConfig {
|
fn token_config(id: &str, url: &str, token: &str, accept_untrusted: bool) -> ConnectionConfig {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
//! Each test spawns a local TLS server with a freshly generated self-signed
|
//! Each test spawns a local TLS server with a freshly generated self-signed
|
||||||
//! certificate and exercises the capture/verify helpers against it.
|
//! certificate and exercises the capture/verify helpers against it.
|
||||||
|
|
||||||
use proxmox_desktop::tls::{
|
use clustri::tls::{
|
||||||
capture_fingerprint, fetch_certificate_info, verify_pin, verify_server_certificate,
|
capture_fingerprint, fetch_certificate_info, verify_pin, verify_server_certificate,
|
||||||
};
|
};
|
||||||
use proxmox_desktop::Error;
|
use clustri::Error;
|
||||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
use rustls::ServerConfig;
|
use rustls::ServerConfig;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export function TerminalConsole({ connectionId, node, vmid, onError, onConnected
|
|||||||
if (!isTauriMode && !cancelled) {
|
if (!isTauriMode && !cancelled) {
|
||||||
// Dev mode mock terminal
|
// Dev mode mock terminal
|
||||||
terminal.writeln('\x1b[1;32m╔══════════════════════════════════════════╗\x1b[0m')
|
terminal.writeln('\x1b[1;32m╔══════════════════════════════════════════╗\x1b[0m')
|
||||||
terminal.writeln('\x1b[1;32m║ ProxmoxDesktop - Terminal Console ║\x1b[0m')
|
terminal.writeln('\x1b[1;32m║ Clustri - Terminal Console ║\x1b[0m')
|
||||||
terminal.writeln('\x1b[1;32m╚══════════════════════════════════════════╝\x1b[0m')
|
terminal.writeln('\x1b[1;32m╚══════════════════════════════════════════╝\x1b[0m')
|
||||||
terminal.writeln('')
|
terminal.writeln('')
|
||||||
terminal.writeln(`\x1b[33mNode: ${node} | VMID: ${vmid} | Type: LXC\x1b[0m`)
|
terminal.writeln(`\x1b[33mNode: ${node} | VMID: ${vmid} | Type: LXC\x1b[0m`)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function Sidebar({ onAddConnection, activeView, onNavigate }: SidebarProp
|
|||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 leading-tight">
|
<div className="min-w-0 leading-tight">
|
||||||
<h1 className="truncate text-sm font-semibold tracking-tight text-foreground">
|
<h1 className="truncate text-sm font-semibold tracking-tight text-foreground">
|
||||||
ProxmoxDesktop
|
Clustri
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[10px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
|
<p className="text-[10px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
|
||||||
Ops Console
|
Ops Console
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { ThemeSwitcher } from '@/components/settings/ThemeSwitcher'
|
import { ThemeSwitcher } from '@/components/settings/ThemeSwitcher'
|
||||||
import { ConnectionManager } from '@/components/settings/ConnectionManager'
|
import { ConnectionManager } from '@/components/settings/ConnectionManager'
|
||||||
import { ExternalLink } from 'lucide-react'
|
|
||||||
|
|
||||||
export function SettingsPage() {
|
export function SettingsPage() {
|
||||||
return (
|
return (
|
||||||
@@ -59,17 +58,6 @@ export function SettingsPage() {
|
|||||||
<span className="text-sm text-muted-foreground">Platform</span>
|
<span className="text-sm text-muted-foreground">Platform</span>
|
||||||
<span className="font-mono text-sm font-medium">Tauri + React</span>
|
<span className="font-mono text-sm font-medium">Tauri + React</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-2 border-t">
|
|
||||||
<a
|
|
||||||
href="https://github.com/proxmoxdesktop"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline-flex items-center gap-1.5 text-sm text-primary hover:underline"
|
|
||||||
>
|
|
||||||
View on GitHub
|
|
||||||
<ExternalLink className="h-3.5 w-3.5" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ function applyTheme(theme: Theme) {
|
|||||||
root.classList.add(theme)
|
root.classList.add(theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem('proxmoxdesktop-theme', theme)
|
localStorage.setItem('clustri-theme', theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialTheme(): Theme {
|
function getInitialTheme(): Theme {
|
||||||
const stored = localStorage.getItem('proxmoxdesktop-theme')
|
const stored = localStorage.getItem('clustri-theme')
|
||||||
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
||||||
return stored
|
return stored
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user