162 lines
5.4 KiB
Svelte
162 lines
5.4 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { api, post, setToken } from './lib/api';
|
|
import Dashboard from './pages/Dashboard.svelte';
|
|
import Nodes from './pages/Nodes.svelte';
|
|
import NodeDetail from './pages/NodeDetail.svelte';
|
|
import Files from './pages/Files.svelte';
|
|
import Terminal from './pages/Terminal.svelte';
|
|
import Logs from './pages/Logs.svelte';
|
|
import Services from './pages/Services.svelte';
|
|
import Nginx from './pages/Nginx.svelte';
|
|
import Docker from './pages/Docker.svelte';
|
|
import Apps from './pages/Apps.svelte';
|
|
import AppStore from './pages/AppStore.svelte';
|
|
import StoreAppDetail from './pages/StoreAppDetail.svelte';
|
|
import AppDetail from './pages/AppDetail.svelte';
|
|
import AppManage from './pages/AppManage.svelte';
|
|
import Audit from './pages/Audit.svelte';
|
|
import Tasks from './pages/Tasks.svelte';
|
|
import Alerts from './pages/Alerts.svelte';
|
|
import Settings from './pages/Settings.svelte';
|
|
import Users from './pages/Users.svelte';
|
|
|
|
let path = location.pathname;
|
|
let me: any = null;
|
|
let loading = true;
|
|
let error = '';
|
|
let username = 'admin';
|
|
let password = '';
|
|
|
|
const nav = [
|
|
['/dashboard', '总览'],
|
|
['/store', '软件商店'],
|
|
['/alerts', '告警'],
|
|
['/tasks', '任务'],
|
|
['/users', '用户'],
|
|
['/audit', '日志'],
|
|
['/settings', '设置']
|
|
];
|
|
|
|
onMount(async () => {
|
|
addEventListener('popstate', () => (path = location.pathname));
|
|
await loadMe();
|
|
});
|
|
|
|
async function loadMe() {
|
|
loading = true;
|
|
try {
|
|
me = await api('/api/auth/me');
|
|
if (path === '/' || path === '/login' || path === '/init') go('/dashboard');
|
|
} catch {
|
|
me = null;
|
|
if (path !== '/init') go('/login');
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
function go(next: string) {
|
|
history.pushState({}, '', next);
|
|
path = next;
|
|
}
|
|
|
|
async function login(init = false) {
|
|
error = '';
|
|
try {
|
|
const data: any = await post(init ? '/api/auth/init' : '/api/auth/login', { username, password });
|
|
setToken(data.token);
|
|
me = data.user;
|
|
go('/dashboard');
|
|
} catch (err: any) {
|
|
error = err.message;
|
|
}
|
|
}
|
|
|
|
function logout() {
|
|
setToken(null);
|
|
me = null;
|
|
go('/login');
|
|
}
|
|
|
|
$: nodeId = path.match(/^\/nodes\/([^/]+)/)?.[1] || '';
|
|
$: nodeAppId = path.match(/^\/nodes\/[^/]+\/apps\/(.+)$/)?.[1] || '';
|
|
$: storeSlug = path.match(/^\/store\/([^/]+)$/)?.[1] || '';
|
|
</script>
|
|
|
|
{#if loading}
|
|
<main class="center">加载中...</main>
|
|
{:else if !me && (path === '/login' || path === '/init')}
|
|
<main class="auth-shell">
|
|
<section class="auth-card">
|
|
<p class="eyebrow">LightOps</p>
|
|
<h1>{path === '/init' ? '初始化管理员' : '登录主控端'}</h1>
|
|
<input bind:value={username} placeholder="用户名" />
|
|
<input bind:value={password} placeholder="密码" type="password" on:keydown={(e) => e.key === 'Enter' && login(path === '/init')} />
|
|
{#if error}<p class="error">{error}</p>{/if}
|
|
<button on:click={() => login(path === '/init')}>{path === '/init' ? '创建管理员' : '登录'}</button>
|
|
<button class="ghost" on:click={() => go(path === '/init' ? '/login' : '/init')}>
|
|
{path === '/init' ? '已有账号,去登录' : '首次使用,初始化'}
|
|
</button>
|
|
</section>
|
|
</main>
|
|
{:else}
|
|
<div class="app-shell">
|
|
<aside>
|
|
<button class="brand" on:click={() => go('/dashboard')}>LightOps</button>
|
|
{#each nav as item}
|
|
<button class:active={path.startsWith(item[0])} on:click={() => go(item[0])}>{item[1]}</button>
|
|
{/each}
|
|
</aside>
|
|
<section class="main">
|
|
<header>
|
|
<span>{me?.username}</span>
|
|
<button class="ghost" on:click={logout}>退出</button>
|
|
</header>
|
|
{#if path.startsWith('/nodes/') && path.endsWith('/files')}
|
|
<Files id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/terminal')}
|
|
<Terminal id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/logs')}
|
|
<Logs id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/services')}
|
|
<Services id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/nginx')}
|
|
<Nginx id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/docker')}
|
|
<Docker id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/tasks')}
|
|
<Tasks id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/apps/manage')}
|
|
<AppManage id={nodeId} />
|
|
{:else if path.startsWith('/nodes/') && path.includes('/apps/') && nodeAppId !== 'manage'}
|
|
<AppDetail id={nodeId} appId={decodeURIComponent(nodeAppId)} />
|
|
{:else if path.startsWith('/nodes/') && path.endsWith('/apps')}
|
|
<Apps id={nodeId} />
|
|
{:else if path.startsWith('/nodes/')}
|
|
<NodeDetail id={nodeId} />
|
|
{:else if path === '/apps'}
|
|
<Apps />
|
|
{:else if path === '/store'}
|
|
<AppStore />
|
|
{:else if storeSlug}
|
|
<StoreAppDetail slug={decodeURIComponent(storeSlug)} />
|
|
{:else if path === '/nodes'}
|
|
<Nodes />
|
|
{:else if path === '/audit'}
|
|
<Audit />
|
|
{:else if path === '/tasks'}
|
|
<Tasks />
|
|
{:else if path === '/alerts'}
|
|
<Alerts />
|
|
{:else if path === '/users'}
|
|
<Users />
|
|
{:else if path === '/settings'}
|
|
<Settings />
|
|
{:else}
|
|
<Dashboard />
|
|
{/if}
|
|
</section>
|
|
</div>
|
|
{/if}
|