imc-vibe/frontend/src/routes/domains/+page.svelte
2026-03-21 22:41:23 +01:00

190 lines
3.9 KiB
Svelte

<script lang="ts">
interface Domain {
id: number;
name: string;
userCount: number;
aliasCount: number;
}
let domains = $state<Domain[]>([]);
let loading = $state(true);
let showModal = $state(false);
let newDomain = $state({ name: '' });
async function loadDomains() {
try {
const res = await fetch('/api/domains');
if (res.ok) {
const data = await res.json();
domains = data.data || [];
}
} catch (e) {
console.error('Failed to load domains:', e);
} finally {
loading = false;
}
}
async function createDomain() {
try {
const res = await fetch('/api/domains', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newDomain)
});
if (res.ok) {
showModal = false;
newDomain = { name: '' };
await loadDomains();
}
} catch (e) {
console.error('Failed to create domain:', e);
}
}
async function deleteDomain(id: number) {
if (!confirm('Delete this domain? Users and aliases will be deleted.')) return;
try {
await fetch(`/api/domains/${id}`, { method: 'DELETE' });
await loadDomains();
} catch (e) {
console.error('Failed to delete domain:', e);
}
}
$effect(() => { loadDomains(); });
</script>
<div class="header">
<h2>Domains</h2>
<button onclick={() => showModal = true}>Add Domain</button>
</div>
{#if loading}
<p>Loading...</p>
{:else if domains.length === 0}
<p>No domains configured yet.</p>
{:else}
<table>
<thead>
<tr>
<th>Domain</th>
<th>Users</th>
<th>Aliases</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each domains as domain}
<tr>
<td><a href="/domains/{domain.name}/users">{domain.name}</a></td>
<td>{domain.userCount}</td>
<td>{domain.aliasCount}</td>
<td>
<button class="danger" onclick={() => deleteDomain(domain.id)}>Delete</button>
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
{#if showModal}
<div class="modal-backdrop" onclick={() => showModal = false}>
<div class="modal" onclick={(e) => e.stopPropagation()}>
<h3>Add Domain</h3>
<form onsubmit={(e) => { e.preventDefault(); createDomain(); }}>
<label>
Domain name:
<input type="text" bind:value={newDomain.name} placeholder="example.org" required />
</label>
<div class="actions">
<button type="button" onclick={() => showModal = false}>Cancel</button>
<button type="submit">Create</button>
</div>
</form>
</div>
</div>
{/if}
<style>
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
table {
width: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background: #f8f9fa;
font-weight: 600;
}
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background: #3498db;
color: white;
}
button.danger {
background: #e74c3c;
}
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.modal {
background: white;
padding: 2rem;
border-radius: 8px;
min-width: 400px;
}
.modal form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.modal label {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.modal input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
}
</style>