实现 LightOps 运维面板基础功能
This commit is contained in:
161
web/src/App.svelte
Normal file
161
web/src/App.svelte
Normal file
@@ -0,0 +1,161 @@
|
||||
<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}
|
||||
Reference in New Issue
Block a user