commit af4bbe9b15a084a96ea4907094e2958f29d384b1 Author: Ahmad Ardiansyah Date: Thu Nov 13 08:48:03 2025 +0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8103a0b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/app.css" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..edc8f0e --- /dev/null +++ b/README.md @@ -0,0 +1,190 @@ +# K3S Management + +A modern web-based management interface for K3S Kubernetes clusters built with SvelteKit 5. + +## Features + +- 🔐 **Authentication** - Secure login system +- 🖥️ **Cluster Management** - Create and manage K3S clusters +- 📦 **Node Management** - Add and monitor cluster nodes +- 🔑 **SSH Configuration** - Manage SSH connections to nodes +- ⚙️ **Settings** - Configure system preferences +- 💾 **Data Export/Import** - Backup and restore configurations + +## Tech Stack + +- **SvelteKit 5** - Frontend framework with modern runes syntax +- **TailwindCSS 4** - Utility-first CSS framework +- **TypeScript** - Type-safe development +- **K3sup** - Backend tool for K3S cluster installation (planned) + +## Getting Started + +### Prerequisites + +- Node.js 18+ or Bun +- A modern web browser + +### Installation + +1. Clone the repository +2. Install dependencies: + +```bash +bun install +# or +npm install +``` + +3. Start the development server: + +```bash +bun run dev +# or +npm run dev +``` + +4. Open your browser and navigate to `http://localhost:5173` + +### Default Login Credentials + +- **Username**: `admin` +- **Password**: `admin` + +⚠️ **Security Note**: Change these credentials in production! + +## Project Structure + +``` +k3s-management/ +├── src/ +│ ├── lib/ +│ │ └── stores/ # Svelte stores for state management +│ │ ├── auth.svelte.ts +│ │ └── clusters.svelte.ts +│ ├── routes/ +│ │ ├── +page.svelte # Login page +│ │ └── admin/ # Admin dashboard +│ │ ├── +layout.svelte +│ │ ├── +page.svelte +│ │ ├── clusters/ +│ │ ├── nodes/ +│ │ ├── ssh/ +│ │ └── settings/ +│ └── app.css # Global styles +└── package.json +``` + +## Usage + +### Creating a Cluster + +1. Navigate to the **Clusters** page +2. Click **Create Cluster** +3. Fill in cluster information: + - Cluster name and description + - K3S version + - Server node SSH details (host, port, username, password) + - Node IP address +4. Click **Create Cluster** + +### Adding Nodes + +1. Navigate to the **Nodes** page +2. Click **Add Node** +3. Select the target cluster +4. Configure node details: + - Node name and role (Server/Agent) + - SSH connection details + - Node IP address +5. Click **Add Node** + +### Managing SSH Configurations + +1. Navigate to **SSH Configurations** +2. View all SSH connections +3. Test connections to verify connectivity +4. Copy connection strings for manual SSH access + +### Settings + +- Configure K3sup version +- Enable/disable automatic backups +- Set up notifications +- Export/import data +- Clear all data + +## Development + +### Building for Production + +```bash +bun run build +# or +npm run build +``` + +### Preview Production Build + +```bash +bun run preview +# or +npm run preview +``` + +### Code Formatting + +```bash +bun run format +# or +npm run format +``` + +### Linting + +```bash +bun run lint +# or +npm run lint +``` + +## Roadmap + +- [ ] Implement actual K3sup backend integration +- [ ] Add real SSH key-based authentication +- [ ] Real-time cluster status monitoring +- [ ] Cluster logs viewer +- [ ] Multi-user support with roles +- [ ] API endpoints for external integrations +- [ ] Kubernetes resources management +- [ ] Helm charts deployment +- [ ] Cluster backup and restore +- [ ] Monitoring and alerting + +## Security Considerations + +⚠️ **Important**: This is a development/demo version. For production use: + +1. Implement proper authentication (JWT, OAuth, etc.) +2. Use SSH keys instead of passwords +3. Encrypt sensitive data +4. Use HTTPS +5. Implement rate limiting +6. Add CSRF protection +7. Validate all inputs +8. Use environment variables for configuration + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +MIT + +## Acknowledgments + +- Built with [SvelteKit](https://kit.svelte.dev/) +- Styled with [TailwindCSS](https://tailwindcss.com/) +- Powered by [K3sup](https://github.com/alexellis/k3sup) +- Icons from [Heroicons](https://heroicons.com/) \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..d57d277 Binary files /dev/null and b/bun.lockb differ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..2c49fa6 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,41 @@ +import prettier from 'eslint-config-prettier'; +import { fileURLToPath } from 'node:url'; +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + rules: { + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off' + } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..b31dcb6 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "k3s-management", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint ." + }, + "devDependencies": { + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.38.0", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.47.1", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.14", + "@types/node": "^22", + "eslint": "^9.38.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.12.4", + "globals": "^16.4.0", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.1", + "svelte": "^5.41.0", + "svelte-check": "^4.3.3", + "tailwindcss": "^4.1.14", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.1", + "vite": "^7.1.10" + } +} diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..1c4d2a8 --- /dev/null +++ b/src/app.css @@ -0,0 +1,2 @@ +@import 'tailwindcss'; +@plugin '@tailwindcss/typography'; diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/stores/auth.svelte.ts b/src/lib/stores/auth.svelte.ts new file mode 100644 index 0000000..8a6edf0 --- /dev/null +++ b/src/lib/stores/auth.svelte.ts @@ -0,0 +1,69 @@ +interface User { + username: string; + role: string; +} + +interface AuthState { + isAuthenticated: boolean; + user: User | null; +} + +class AuthStore { + private state = $state({ + isAuthenticated: false, + user: null + }); + + get isAuthenticated() { + return this.state.isAuthenticated; + } + + get user() { + return this.state.user; + } + + login(username: string, password: string): boolean { + // Temporary simple auth - akan diganti dengan backend auth + if (username === 'admin' && password === 'admin') { + this.state.isAuthenticated = true; + this.state.user = { + username: username, + role: 'admin' + }; + + // Store in localStorage + if (typeof window !== 'undefined') { + localStorage.setItem('auth', JSON.stringify(this.state.user)); + } + + return true; + } + return false; + } + + logout() { + this.state.isAuthenticated = false; + this.state.user = null; + + if (typeof window !== 'undefined') { + localStorage.removeItem('auth'); + } + } + + checkAuth() { + if (typeof window !== 'undefined') { + const stored = localStorage.getItem('auth'); + if (stored) { + try { + const user = JSON.parse(stored); + this.state.isAuthenticated = true; + this.state.user = user; + } catch { + this.logout(); + } + } + } + } +} + +export const authStore = new AuthStore(); diff --git a/src/lib/stores/clusters.svelte.ts b/src/lib/stores/clusters.svelte.ts new file mode 100644 index 0000000..482d560 --- /dev/null +++ b/src/lib/stores/clusters.svelte.ts @@ -0,0 +1,137 @@ +export interface SSHConfig { + host: string; + port: number; + username: string; + password?: string; + privateKey?: string; +} + +export interface ClusterNode { + id: string; + name: string; + role: 'server' | 'agent'; + ssh: SSHConfig; + status: 'pending' | 'installing' | 'active' | 'failed' | 'stopped'; + ip: string; + createdAt: Date; +} + +export interface K3SCluster { + id: string; + name: string; + description: string; + status: 'creating' | 'active' | 'failed' | 'stopped'; + nodes: ClusterNode[]; + createdAt: Date; + k3sVersion?: string; +} + +class ClustersStore { + private state = $state<{ + clusters: K3SCluster[]; + selectedCluster: K3SCluster | null; + loading: boolean; + }>({ + clusters: [], + selectedCluster: null, + loading: false + }); + + get clusters() { + return this.state.clusters; + } + + get selectedCluster() { + return this.state.selectedCluster; + } + + get loading() { + return this.state.loading; + } + + addCluster(cluster: K3SCluster) { + this.state.clusters.push(cluster); + this.saveToStorage(); + } + + updateCluster(id: string, updates: Partial) { + const index = this.state.clusters.findIndex((c) => c.id === id); + if (index !== -1) { + this.state.clusters[index] = { ...this.state.clusters[index], ...updates }; + this.saveToStorage(); + } + } + + deleteCluster(id: string) { + this.state.clusters = this.state.clusters.filter((c) => c.id !== id); + if (this.state.selectedCluster?.id === id) { + this.state.selectedCluster = null; + } + this.saveToStorage(); + } + + selectCluster(id: string) { + this.state.selectedCluster = this.state.clusters.find((c) => c.id === id) || null; + } + + addNodeToCluster(clusterId: string, node: ClusterNode) { + const cluster = this.state.clusters.find((c) => c.id === clusterId); + if (cluster) { + cluster.nodes.push(node); + this.saveToStorage(); + } + } + + updateNodeStatus(clusterId: string, nodeId: string, status: ClusterNode['status']) { + const cluster = this.state.clusters.find((c) => c.id === clusterId); + if (cluster) { + const node = cluster.nodes.find((n) => n.id === nodeId); + if (node) { + node.status = status; + this.saveToStorage(); + } + } + } + + removeNodeFromCluster(clusterId: string, nodeId: string) { + const cluster = this.state.clusters.find((c) => c.id === clusterId); + if (cluster) { + cluster.nodes = cluster.nodes.filter((n) => n.id !== nodeId); + this.saveToStorage(); + } + } + + setLoading(loading: boolean) { + this.state.loading = loading; + } + + loadFromStorage() { + if (typeof window !== 'undefined') { + const stored = localStorage.getItem('k3s-clusters'); + if (stored) { + try { + const data = JSON.parse(stored); + // Convert date strings back to Date objects + this.state.clusters = data.map((c: any) => ({ + ...c, + createdAt: new Date(c.createdAt), + nodes: c.nodes.map((n: any) => ({ + ...n, + createdAt: new Date(n.createdAt) + })) + })); + } catch (e) { + console.error('Failed to load clusters from storage:', e); + } + } + } + } + + private saveToStorage() { + if (typeof window !== 'undefined') { + localStorage.setItem('k3s-clusters', JSON.stringify(this.state.clusters)); + } + } +} + +export const clustersStore = new ClustersStore(); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..0cf092d --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + K3S Management + + + +{@render children()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..0880bc9 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,169 @@ + + +
+
+ +
+
+
+ +
+
+ + + +
+

K3S Management

+

Manage your Kubernetes clusters with ease

+
+ + +
+ {#if error} +
+ + + + {error} +
+ {/if} + +
+ + +
+ +
+ + +
+ + +
+ + +
+

+ Default credentials: admin / admin +

+
+
+
+ + +

Powered by K3sup & SvelteKit

+
+
+ + diff --git a/src/routes/admin/+layout.svelte b/src/routes/admin/+layout.svelte new file mode 100644 index 0000000..bdc8a4f --- /dev/null +++ b/src/routes/admin/+layout.svelte @@ -0,0 +1,193 @@ + + +
+ + + + +
+ +
+ + +
+
+

Welcome back,

+

{authStore.user?.username || 'Admin'}

+
+
+
+ + +
+ {@render children()} +
+
+
diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte new file mode 100644 index 0000000..3af2a0c --- /dev/null +++ b/src/routes/admin/+page.svelte @@ -0,0 +1,273 @@ + + +
+ +
+

Dashboard

+

Overview of your K3S infrastructure

+
+ + +
+ +
+
+
+

Total Clusters

+

{stats.totalClusters}

+
+
+ + + +
+
+
+ + +
+
+
+

Active Clusters

+

{stats.activeClusters}

+
+
+ + + +
+
+
+ + +
+
+
+

Total Nodes

+

{stats.totalNodes}

+
+
+ + + +
+
+
+ + +
+
+
+

Active Nodes

+

{stats.activeNodes}

+
+
+ + + +
+
+
+
+ + + + + + {#if clustersStore.clusters.length > 0} +
+
+

Recent Clusters

+ + View all → + +
+
+ {#each clustersStore.clusters.slice(0, 5) as cluster} +
+
+
+ + + +
+
+

{cluster.name}

+

+ {cluster.nodes.length} nodes • {cluster.status} +

+
+
+ + Manage + +
+ {/each} +
+
+ {:else} +
+
+ + + +
+

No Clusters Yet

+

Get started by creating your first K3S cluster

+ + + + + Create Your First Cluster + +
+ {/if} +
diff --git a/src/routes/admin/clusters/+page.svelte b/src/routes/admin/clusters/+page.svelte new file mode 100644 index 0000000..9b7dee2 --- /dev/null +++ b/src/routes/admin/clusters/+page.svelte @@ -0,0 +1,499 @@ + + +
+ +
+
+

K3S Clusters

+

Manage your Kubernetes clusters

+
+ +
+ + + {#if clustersStore.clusters.length > 0} +
+ {#each clustersStore.clusters as cluster} +
+
+
+
+

{cluster.name}

+ + {cluster.status} + +
+

{cluster.description}

+
+ +
+ +
+
+

Nodes

+

{cluster.nodes.length}

+
+
+

K3S Version

+

{cluster.k3sVersion || 'N/A'}

+
+
+

Active Nodes

+

+ {cluster.nodes.filter((n) => n.status === 'active').length} +

+
+
+

Created

+

+ {cluster.createdAt.toLocaleDateString()} +

+
+
+ + +
+

Nodes

+ {#each cluster.nodes as node} +
+
+
+ + + +
+
+

{node.name}

+

{node.ip} • {node.role}

+
+
+ + {node.status} + +
+ {/each} +
+ + +
+ {/each} +
+ {:else} +
+
+ + + +
+

No Clusters Yet

+

+ Create your first K3S cluster to get started with container orchestration +

+ +
+ {/if} +
+ + +{#if showCreateModal} +
e.target === e.currentTarget && closeCreateModal()} + > +
e.stopPropagation()} + > +
+
+

Create New Cluster

+ +
+
+ +
+
+ +
+

Cluster Information

+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+

Server Node (Master)

+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +

+ Note: In production, consider using SSH keys instead of passwords +

+
+
+
+
+ +
+ + +
+
+
+
+{/if} + + +{#if showDeleteModal && clusterToDelete} +
e.target === e.currentTarget && closeDeleteModal()} + > +
e.stopPropagation()} + > +
+
+ + + +
+

Delete Cluster

+

+ Are you sure you want to delete cluster "{clusterToDelete.name}"? This action cannot be undone. +

+
+ + +
+
+
+
+{/if} diff --git a/src/routes/admin/nodes/+page.svelte b/src/routes/admin/nodes/+page.svelte new file mode 100644 index 0000000..9705083 --- /dev/null +++ b/src/routes/admin/nodes/+page.svelte @@ -0,0 +1,562 @@ + + +
+ +
+
+

Nodes

+

Manage nodes across all clusters

+
+ +
+ + +
+
+
+
+

Total Nodes

+

{allNodes.length}

+
+
+ + + +
+
+
+ +
+
+
+

Active Nodes

+

+ {allNodes.filter((n) => n.status === 'active').length} +

+
+
+ + + +
+
+
+ +
+
+
+

Server Nodes

+

+ {allNodes.filter((n) => n.role === 'server').length} +

+
+
+ + + +
+
+
+ +
+
+
+

Agent Nodes

+

+ {allNodes.filter((n) => n.role === 'agent').length} +

+
+
+ + + +
+
+
+
+ + + {#if allNodes.length > 0} +
+
+ + + + + + + + + + + + + + {#each allNodes as node} + + + + + + + + + + {/each} + +
+ Node Name + + Cluster + + IP Address + + Role + + Status + + Created + + Actions +
+
+
+ + + +
+
+

{node.name}

+

{node.ssh.username}@{node.ssh.host}

+
+
+
+
+

{node.clusterName}

+

{node.clusterStatus}

+
+
+

{node.ip}

+
+ + {node.role} + + + + {node.status} + + +

{node.createdAt.toLocaleDateString()}

+
+ +
+
+
+ {:else} +
+
+ + + +
+

No Nodes Yet

+

+ {#if clustersStore.clusters.length === 0} + Create a cluster first before adding nodes + {:else} + Add nodes to your clusters to expand your infrastructure + {/if} +

+ {#if clustersStore.clusters.length > 0} + + {:else} + + Create Cluster + + {/if} +
+ {/if} +
+ + +{#if showAddNodeModal} +
e.target === e.currentTarget && closeAddNodeModal()} + > +
e.stopPropagation()} + > +
+
+

Add New Node

+ +
+
+ +
+
+ +
+ + +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+

SSH Configuration

+
+
+
+ + +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+
+{/if} diff --git a/src/routes/admin/settings/+page.svelte b/src/routes/admin/settings/+page.svelte new file mode 100644 index 0000000..9e7b5f1 --- /dev/null +++ b/src/routes/admin/settings/+page.svelte @@ -0,0 +1,360 @@ + + +
+ +
+

Settings

+

Configure your K3S management system

+
+ + + {#if showSaveMessage} +
+ + + + Settings saved successfully! +
+ {/if} + +
+ +
+

K3sup Configuration

+
+
+ + +

+ Version of k3sup to use for cluster installation +

+
+ +
+

About K3sup

+

+ k3sup is a light-weight utility to get from zero to KUBE with k3s on any local or + remote VM. All you need is ssh access and the k3sup binary to get kubectl access + immediately. +

+ + Learn more + + + + +
+
+
+ + +
+

Backup Settings

+
+
+
+

Automatic Backup

+

Automatically backup cluster configurations

+
+ +
+ +
+ + +
+
+
+ + +
+

Notifications

+
+
+
+

Enable Notifications

+

Receive alerts about cluster events

+
+ +
+ +
+ + +
+
+
+ + +
+

Appearance

+
+ + +

Currently only dark mode is supported

+
+
+ + +
+ +
+
+ + +
+

Data Management

+
+
+ + + +
+ + +
+
+ + +
+

System Information

+
+
+ Application Version + v0.0.1 +
+
+ Total Clusters + {clustersStore.clusters.length} +
+
+ Total Nodes + + {clustersStore.clusters.reduce((acc, c) => acc + c.nodes.length, 0)} + +
+
+ Current User + {authStore.user?.username || 'N/A'} +
+
+
+
diff --git a/src/routes/admin/ssh/+page.svelte b/src/routes/admin/ssh/+page.svelte new file mode 100644 index 0000000..18c74ab --- /dev/null +++ b/src/routes/admin/ssh/+page.svelte @@ -0,0 +1,470 @@ + + +
+ +
+

SSH Configurations

+

Manage SSH connections to your nodes

+
+ + +
+ + + +
+

SSH Security Notice

+

+ For production environments, it's recommended to use SSH key-based authentication instead of + passwords. Store your private keys securely and never commit them to version control. +

+
+
+ + +
+
+
+
+

Total Connections

+

{allSSHConfigs.length}

+
+
+ + + +
+
+
+ +
+
+
+

Active Connections

+

+ {allSSHConfigs.filter((c) => c.status === 'active').length} +

+
+
+ + + +
+
+
+ +
+
+
+

Unique Hosts

+

+ {new Set(allSSHConfigs.map((c) => c.ssh.host)).size} +

+
+
+ + + +
+
+
+
+ + + {#if allSSHConfigs.length > 0} +
+
+ + + + + + + + + + + + + {#each allSSHConfigs as config} + + + + + + + + + {/each} + +
+ Node + + Cluster + + SSH Connection + + IP Address + + Status + + Actions +
+
+
+ + + +
+
+

{config.nodeName}

+
+
+
+

{config.clusterName}

+
+
+ + {config.ssh.username}@{config.ssh.host}:{config.ssh.port} + + +
+
+ {config.ip} + +
+
+ + {config.status} + +
+
+ +
+
+
+ {:else} +
+
+ + + +
+

No SSH Configurations

+

+ Create a cluster and add nodes to see SSH configurations here +

+ + Go to Clusters + +
+ {/if} + + +
+

SSH Commands Reference

+
+
+

Connect to a node via SSH:

+ + ssh username@host -p port + +
+
+

Connect using SSH key:

+ + ssh -i /path/to/private_key username@host -p port + +
+
+

Generate SSH key pair:

+ + ssh-keygen -t ed25519 -C "your_email@example.com" + +
+
+

Copy SSH key to remote host:

+ + ssh-copy-id username@host + +
+
+
+
+ + +{#if showTestModal} +
e.target === e.currentTarget && closeTestModal()} + > +
e.stopPropagation()} + > +
+
+

Test SSH Connection

+ +
+
+ +
+ {#if testingConfig} +
+

Testing connection to:

+ + {testingConfig.username}@{testingConfig.host} + +
+ {/if} + + {#if testResult === null} +
+ + + + +

Testing connection...

+
+ {:else if testResult.success} +
+
+ + + +
+

Connection Successful

+

{testResult.message}

+
+
+
+ {:else} +
+
+ + + +
+

Connection Failed

+

{testResult.message}

+
+
+
+ {/if} + + {#if testResult !== null} + + {/if} +
+
+
+{/if} diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..a0133e6 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,17 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a5567ee --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..2d35c4f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import tailwindcss from '@tailwindcss/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()] +});