2026-07-18 16:40:09 +08:00
|
|
|
<script lang="ts">
|
|
|
|
|
export let apiBase: string;
|
2026-07-19 19:01:27 +08:00
|
|
|
export let onLogin: (detail: { apiBase: string; account: string }) => void = () => {};
|
2026-07-18 16:40:09 +08:00
|
|
|
|
|
|
|
|
let server = apiBase;
|
2026-07-19 19:01:27 +08:00
|
|
|
let account = '';
|
|
|
|
|
let password = '';
|
|
|
|
|
let error = '';
|
2026-07-19 19:59:38 +08:00
|
|
|
let connectionStatus = 'Not connected. Enter a server address to continue.';
|
2026-07-18 16:40:09 +08:00
|
|
|
|
2026-07-19 19:59:38 +08:00
|
|
|
function normalizeServer(value: string): { apiBase?: string; error?: string } {
|
2026-07-18 16:40:09 +08:00
|
|
|
const trimmed = value.trim();
|
2026-07-19 19:59:38 +08:00
|
|
|
if (!trimmed) {
|
|
|
|
|
return { error: 'Enter a server address.' };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const candidate = /^https?:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const parsed = new URL(candidate);
|
|
|
|
|
if (!parsed.hostname || !['http:', 'https:'].includes(parsed.protocol)) {
|
|
|
|
|
return { error: 'Enter a valid HTTP or HTTPS server address.' };
|
|
|
|
|
}
|
|
|
|
|
return { apiBase: candidate };
|
|
|
|
|
} catch {
|
|
|
|
|
return { error: 'Enter a valid HTTP or HTTPS server address.' };
|
2026-07-18 16:40:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-07-19 19:01:27 +08:00
|
|
|
|
|
|
|
|
function submitLogin() {
|
|
|
|
|
error = '';
|
2026-07-19 19:59:38 +08:00
|
|
|
const normalized = normalizeServer(server);
|
|
|
|
|
if (normalized.error) {
|
|
|
|
|
error = normalized.error;
|
|
|
|
|
connectionStatus = 'Check the server address before signing in.';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 19:01:27 +08:00
|
|
|
if (!account.trim() || !password.trim()) {
|
|
|
|
|
error = 'Enter an account and password.';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 19:59:38 +08:00
|
|
|
server = normalized.apiBase ?? server;
|
|
|
|
|
connectionStatus = 'Connection settings are ready.';
|
|
|
|
|
onLogin({ apiBase: server, account: account.trim() });
|
2026-07-19 19:01:27 +08:00
|
|
|
}
|
2026-07-18 16:40:09 +08:00
|
|
|
</script>
|
|
|
|
|
|
2026-07-19 19:01:27 +08:00
|
|
|
<main class="login-page">
|
|
|
|
|
<section class="login-panel" aria-labelledby="login-title">
|
|
|
|
|
<div class="login-brand" aria-hidden="true">SA</div>
|
|
|
|
|
<div class="login-heading">
|
|
|
|
|
<p>Private workbench</p>
|
|
|
|
|
<h1 id="login-title">SenlinAI Workbench</h1>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form class="server-login" aria-label="Server login" on:submit|preventDefault={submitLogin}>
|
|
|
|
|
<label for="server-address">Server IP or domain</label>
|
|
|
|
|
<input id="server-address" name="server" bind:value={server} placeholder="http://localhost:8080" />
|
|
|
|
|
|
|
|
|
|
<label for="login-account">Email or username</label>
|
|
|
|
|
<input id="login-account" name="account" bind:value={account} autocomplete="username" />
|
|
|
|
|
|
|
|
|
|
<label for="login-password">Password</label>
|
|
|
|
|
<input
|
|
|
|
|
id="login-password"
|
|
|
|
|
name="password"
|
|
|
|
|
type="password"
|
|
|
|
|
bind:value={password}
|
|
|
|
|
autocomplete="current-password"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{#if error}
|
|
|
|
|
<p class="form-error" aria-live="polite">{error}</p>
|
|
|
|
|
{/if}
|
|
|
|
|
|
2026-07-19 19:59:38 +08:00
|
|
|
<p class="connection-status" role="status">{connectionStatus}</p>
|
|
|
|
|
|
2026-07-19 19:01:27 +08:00
|
|
|
<button type="submit">Log in</button>
|
|
|
|
|
<p class="login-note">The server address is remembered on this device.</p>
|
|
|
|
|
</form>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|