Files
lightOps/web/src/App.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}