274 lines
7.1 KiB
Svelte
274 lines
7.1 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { CaddyService, configStore, upstreamsStore } from './CaddyService';
|
|
import {
|
|
Button,
|
|
Card,
|
|
Tabs,
|
|
TabItem,
|
|
Table,
|
|
TableBody,
|
|
TableBodyCell,
|
|
TableBodyRow,
|
|
TableHead,
|
|
TableHeadCell,
|
|
Modal,
|
|
Input,
|
|
Textarea,
|
|
Spinner
|
|
} from 'flowbite-svelte';
|
|
import type { Upstream, CAInfo } from './CaddyService';
|
|
|
|
// State variables
|
|
let config = {};
|
|
let upstreams: Upstream[] = [];
|
|
let caInfo: CAInfo | null = null;
|
|
let caCertificates = '';
|
|
let showConfigModal = false;
|
|
let showCAModal = false;
|
|
let loadingConfig = false;
|
|
let loadingUpstreams = false;
|
|
let newConfig = '';
|
|
let configPath = '';
|
|
let caId = 'local';
|
|
let activeTab = 'config'; // Tracks the active tab
|
|
|
|
// Subscribe to stores
|
|
configStore.subscribe((value: any) => (config = value));
|
|
upstreamsStore.subscribe((value: any) => (upstreams = value));
|
|
|
|
onMount(async () => {
|
|
await refreshData();
|
|
});
|
|
|
|
// Fetch and refresh data
|
|
async function refreshData() {
|
|
loadingConfig = true;
|
|
try {
|
|
await CaddyService.getConfig();
|
|
await CaddyService.getUpstreams();
|
|
} catch (error) {
|
|
alert(`Error refreshing data: ${(error as Error).message}`);
|
|
} finally {
|
|
loadingConfig = false;
|
|
loadingUpstreams = false;
|
|
}
|
|
}
|
|
|
|
// Update configuration
|
|
async function handleUpdateConfig() {
|
|
try {
|
|
loadingConfig = true;
|
|
await CaddyService.updateConfig(configPath, JSON.parse(newConfig));
|
|
await refreshData();
|
|
showConfigModal = false;
|
|
} catch (error) {
|
|
alert(`Error updating config: ${(error as Error).message}`);
|
|
} finally {
|
|
loadingConfig = false;
|
|
}
|
|
}
|
|
|
|
// Load new configuration
|
|
async function handleLoadConfig() {
|
|
try {
|
|
loadingConfig = true;
|
|
await CaddyService.loadConfig(JSON.parse(newConfig));
|
|
await refreshData();
|
|
showConfigModal = false;
|
|
} catch (error) {
|
|
alert(`Error loading config: ${(error as Error).message}`);
|
|
} finally {
|
|
loadingConfig = false;
|
|
}
|
|
}
|
|
|
|
// Stop the Caddy server
|
|
async function handleStopServer() {
|
|
if (confirm('Are you sure you want to stop the Caddy server?')) {
|
|
try {
|
|
await CaddyService.stopServer();
|
|
alert('Caddy server stopped successfully');
|
|
} catch (error) {
|
|
alert(`Error stopping server: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fetch CA information
|
|
async function handleGetCAInfo() {
|
|
try {
|
|
caInfo = await CaddyService.getCAInfo(caId);
|
|
caCertificates = await CaddyService.getCACertificates(caId);
|
|
showCAModal = true;
|
|
} catch (error) {
|
|
alert(`Error fetching CA info: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
|
|
// Change active tab
|
|
function handleTabChange(tab: string) {
|
|
activeTab = tab;
|
|
}
|
|
</script>
|
|
|
|
<main class="container mx-auto min-h-screen bg-gray-50 p-6">
|
|
<header class="mb-6">
|
|
<h1 class="text-caddy-green text-4xl font-bold">Caddy Dashboard</h1>
|
|
<p class="mt-2 text-gray-700">
|
|
Manage your Caddy server configurations, upstreams, and CA information easily.
|
|
</p>
|
|
</header>
|
|
|
|
<Tabs style="pills">
|
|
<TabItem
|
|
open={activeTab === 'config'}
|
|
on:click={() => handleTabChange('config')}
|
|
title="Configuration"
|
|
/>
|
|
<TabItem
|
|
open={activeTab === 'upstreams'}
|
|
on:click={() => handleTabChange('upstreams')}
|
|
title="Upstreams"
|
|
/>
|
|
<TabItem
|
|
open={activeTab === 'ca_management'}
|
|
on:click={() => handleTabChange('ca_management')}
|
|
title="CA Management"
|
|
/>
|
|
</Tabs>
|
|
|
|
<!-- Tab Content -->
|
|
{#if activeTab === 'config'}
|
|
<section class="mt-6">
|
|
<Card class="shadow-lg">
|
|
<header class="mb-4 flex items-center justify-between">
|
|
<h2 class="text-caddy-blue text-2xl font-semibold">Current Configuration</h2>
|
|
<Button on:click={() => (showConfigModal = true)} class="flex items-center gap-2">
|
|
Update Configuration
|
|
</Button>
|
|
</header>
|
|
{#if loadingConfig}
|
|
<div class="flex justify-center p-4">
|
|
<Spinner size="xl" color="blue" />
|
|
</div>
|
|
{:else}
|
|
<pre class="overflow-x-auto rounded-lg bg-gray-100 p-4 text-sm">
|
|
{JSON.stringify(config, null, 2)}
|
|
</pre>
|
|
{/if}
|
|
</Card>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if activeTab === 'upstreams'}
|
|
<section class="mt-6">
|
|
<Card class="shadow-lg">
|
|
<header class="mb-4">
|
|
<h2 class="text-caddy-blue text-2xl font-semibold">Upstreams</h2>
|
|
</header>
|
|
{#if loadingUpstreams}
|
|
<div class="flex justify-center p-4">
|
|
<Spinner size="xl" color="blue" />
|
|
</div>
|
|
{:else}
|
|
<Table striped hoverable>
|
|
<TableHead>
|
|
<TableHeadCell>Address</TableHeadCell>
|
|
<TableHeadCell>Requests</TableHeadCell>
|
|
<TableHeadCell>Fails</TableHeadCell>
|
|
</TableHead>
|
|
<TableBody>
|
|
{#each upstreams as upstream}
|
|
<TableBodyRow>
|
|
<TableBodyCell>{upstream.address}</TableBodyCell>
|
|
<TableBodyCell>{upstream.num_requests}</TableBodyCell>
|
|
<TableBodyCell>{upstream.fails}</TableBodyCell>
|
|
</TableBodyRow>
|
|
{/each}
|
|
</TableBody>
|
|
</Table>
|
|
{/if}
|
|
</Card>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if activeTab === 'ca_management'}
|
|
<section class="mt-6">
|
|
<Card class="shadow-lg">
|
|
<header class="mb-4">
|
|
<h2 class="text-caddy-blue text-2xl font-semibold">CA Management</h2>
|
|
</header>
|
|
<div class="flex items-center gap-4">
|
|
<Input
|
|
type="text"
|
|
placeholder="Enter CA ID (e.g., local)"
|
|
bind:value={caId}
|
|
class="flex-1"
|
|
/>
|
|
<Button on:click={handleGetCAInfo}>Get CA Info</Button>
|
|
</div>
|
|
</Card>
|
|
</section>
|
|
{/if}
|
|
|
|
<footer class="mt-6 flex justify-end">
|
|
<Button color="red" on:click={handleStopServer} class="flex items-center gap-2">
|
|
Stop Caddy Server
|
|
</Button>
|
|
</footer>
|
|
</main>
|
|
|
|
<!-- Modals -->
|
|
<Modal bind:open={showConfigModal} size="xl" autoclose={false}>
|
|
<h2 class="text-caddy-blue mb-4 text-2xl font-semibold">Update Configuration</h2>
|
|
<Input
|
|
type="text"
|
|
placeholder="Config path (e.g., apps/http/servers/myserver/listen)"
|
|
bind:value={configPath}
|
|
class="mb-4"
|
|
/>
|
|
<Textarea
|
|
rows={10}
|
|
placeholder="Enter new configuration (JSON format)"
|
|
bind:value={newConfig}
|
|
class="mb-4"
|
|
/>
|
|
<div class="flex justify-end gap-4">
|
|
<Button on:click={() => (showConfigModal = false)}>Cancel</Button>
|
|
<Button color="green" on:click={handleUpdateConfig}>Update</Button>
|
|
<Button color="blue" on:click={handleLoadConfig}>Load Full Config</Button>
|
|
</div>
|
|
</Modal>
|
|
|
|
<Modal bind:open={showCAModal} size="xl" autoclose={false}>
|
|
<h2 class="text-caddy-blue mb-4 text-2xl font-semibold">CA Information</h2>
|
|
{#if caInfo}
|
|
<div class="space-y-4">
|
|
<p><strong>ID:</strong> {caInfo.id}</p>
|
|
<p><strong>Name:</strong> {caInfo.name}</p>
|
|
<p><strong>Root CN:</strong> {caInfo.root_common_name}</p>
|
|
<p><strong>Intermediate CN:</strong> {caInfo.intermediate_common_name}</p>
|
|
</div>
|
|
<h3 class="text-caddy-blue mt-4 text-lg font-semibold">Certificates</h3>
|
|
<Textarea readonly rows={10} value={caCertificates} />
|
|
{:else}
|
|
<p>No CA information available.</p>
|
|
{/if}
|
|
<div class="mt-4 flex justify-end">
|
|
<Button on:click={() => (showCAModal = false)}>Close</Button>
|
|
</div>
|
|
</Modal>
|
|
|
|
<style>
|
|
:global(body) {
|
|
background-color: #f3f4f6;
|
|
}
|
|
:global(.text-caddy-green) {
|
|
color: #00add8;
|
|
}
|
|
:global(.text-caddy-blue) {
|
|
color: #0097b7;
|
|
}
|
|
</style>
|