Setup unifi wlans

This commit is contained in:
2026-02-22 19:12:13 -06:00
parent a99c9f5102
commit 6791a6735b
38 changed files with 24435 additions and 1663 deletions
@@ -0,0 +1,376 @@
<script lang="ts">
import type { ConfigurationData } from "../types";
import { formatDate, configStatusClass } from "../types";
export let configurations: ConfigurationData[];
export let isMobile: boolean;
// Configurations split-view state
let selectedConfig: ConfigurationData | null = null;
let configFadeKey = 0;
// Track which password fields are revealed (by question id)
let revealedPasswords: Record<number, boolean> = {};
function togglePassword(questionId: number) {
revealedPasswords[questionId] = !revealedPasswords[questionId];
revealedPasswords = revealedPasswords;
}
function selectConfig(config: ConfigurationData) {
if (selectedConfig?.id === config.id) {
selectedConfig = null;
} else {
selectedConfig = config;
configFadeKey++;
revealedPasswords = {};
}
}
</script>
{#if configurations.length === 0}
<div class="tab-empty">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="tab-empty-icon"
>
<path
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
<p>No configurations found</p>
</div>
{:else}
<div
class="config-split"
class:expanded={selectedConfig !== null && !isMobile}
>
<!-- Left side: config buttons -->
<div
class="config-list"
class:collapsed={selectedConfig !== null && !isMobile}
>
{#each configurations as config (config.id)}
<button
class="config-item"
class:selected={selectedConfig?.id === config.id}
class:config-inactive={config.status?.name === "Inactive" ||
config.status?.name === "Automate Inactive"}
on:click={() => selectConfig(config)}
type="button"
>
<div class="config-item-header">
<div class="config-name-group">
<span
class="config-status-dot dot-{configStatusClass(
config.status?.name,
)}"
title={config.status?.name ?? "Unknown"}
></span>
<span class="config-name">{config.name}</span>
</div>
<div class="config-header-badges">
{#if config.status?.name && (!selectedConfig || isMobile)}
<span
class="config-status-badge status-{configStatusClass(
config.status.name,
)}">{config.status.name}</span
>
{/if}
{#if config.type?.name && (!selectedConfig || isMobile)}
<span class="config-type-badge">{config.type.name}</span>
{/if}
</div>
</div>
{#if !selectedConfig || isMobile}
{#if config.description}
<p class="config-description">{config.description}</p>
{/if}
{#if config.key}
<div class="config-kv">
<span class="config-key">{config.key}</span>
{#if config.value}
<span class="config-value">{config.value}</span>
{/if}
</div>
{/if}
{#if formatDate(config.updatedAt) || formatDate(config.createdAt) || formatDate(config.info?.lastUpdated) || formatDate(config.info?.dateEntered)}
<span class="config-date">
{#if formatDate(config.updatedAt)}
Updated {formatDate(config.updatedAt)}
{:else if formatDate(config.info?.lastUpdated)}
Updated {formatDate(config.info?.lastUpdated)}
{:else if formatDate(config.createdAt)}
Created {formatDate(config.createdAt)}
{:else if formatDate(config.info?.dateEntered)}
Created {formatDate(config.info?.dateEntered)}
{/if}
</span>
{/if}
{/if}
</button>
{/each}
</div>
<!-- Right side: config detail panel -->
{#if selectedConfig}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class="bottom-sheet-overlay"
class:active={selectedConfig !== null}
on:click={() => {
selectedConfig = null;
}}
>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="bottom-sheet-panel" on:click|stopPropagation>
<div class="bottom-sheet-handle"></div>
<div class="bottom-sheet-body">
<div class="config-detail-panel">
{#key configFadeKey}
<div class="config-detail-content">
<div class="config-detail-header">
<div class="config-detail-header-left">
<h3 class="config-detail-title">
{selectedConfig.name}
</h3>
<div class="config-detail-meta-badges">
{#if selectedConfig.type?.name}
<span class="config-badge type"
>{selectedConfig.type.name}</span
>
{/if}
{#if selectedConfig.status?.name}
<span
class="config-badge status-{configStatusClass(
selectedConfig.status.name,
)}"
>
{selectedConfig.status.name}
</span>
{/if}
</div>
</div>
<button
class="config-detail-close"
on:click={() => (selectedConfig = null)}
aria-label="Close detail view"
type="button"
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="16"
height="16"
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
</div>
{#if selectedConfig.serialNumber}
<div class="config-serial">
<span class="config-serial-label">Serial #</span>
<span class="config-serial-value"
>{selectedConfig.serialNumber}</span
>
</div>
{/if}
<!-- Questions / Fields -->
{#if (selectedConfig.questions && selectedConfig.questions.length > 0) || selectedConfig.notes}
<div class="config-questions">
{#if selectedConfig.notes}
<div class="config-notes">
<h4 class="config-section-title">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="16"
height="16"
>
<path
d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10 9 9 9 8 9" />
</svg>
Notes
</h4>
<p class="config-notes-text">
{selectedConfig.notes}
</p>
</div>
{/if}
<h4 class="config-section-title">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="16"
height="16"
>
<path
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"
/>
<rect x="9" y="3" width="6" height="4" rx="1" />
</svg>
Configuration Details
</h4>
<div class="questions-grid">
{#each selectedConfig.questions as q (q.id)}
<div
class="question-row"
class:has-answer={!!q.answer}
>
<span class="question-label">{q.question}</span>
<div class="question-value-wrap">
{#if q.fieldType === "Password"}
<span class="question-value password-value">
{#if revealedPasswords[q.id]}
{q.answer || "—"}
{:else}
{q.answer ? "••••••••" : "—"}
{/if}
</span>
{#if q.answer}
<button
class="password-toggle"
on:click={() => togglePassword(q.id)}
type="button"
aria-label={revealedPasswords[q.id]
? "Hide password"
: "Show password"}
>
{#if revealedPasswords[q.id]}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="14"
height="14"
>
<path
d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94"
/>
<path
d="M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19"
/>
<path
d="M14.12 14.12a3 3 0 11-4.24-4.24"
/>
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
{:else}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="14"
height="14"
>
<path
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
/>
<circle cx="12" cy="12" r="3" />
</svg>
{/if}
</button>
{/if}
{:else if q.fieldType === "TextArea"}
<span class="question-value textarea-value"
>{q.answer || "—"}</span
>
{:else}
<span class="question-value"
>{q.answer || "—"}</span
>
{/if}
</div>
</div>
{/each}
</div>
</div>
{:else if !selectedConfig.notes}
<div class="config-no-questions">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
width="32"
height="32"
>
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
<p>No configuration fields available</p>
</div>
{/if}
<!-- Footer metadata -->
{#if selectedConfig.info}
<div class="config-info-footer">
{#if selectedConfig.info.enteredBy || selectedConfig.info.dateEntered}
<div class="config-info-item">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="13"
height="13"
>
<path d="M12 5v14M5 12h14" />
</svg>
Created{#if selectedConfig.info.enteredBy}&nbsp;by
<strong>{selectedConfig.info.enteredBy}</strong
>{/if}{#if selectedConfig.info.dateEntered}&nbsp;on
{formatDate(selectedConfig.info.dateEntered)}{/if}
</div>
{/if}
{#if selectedConfig.info.updatedBy || selectedConfig.info.lastUpdated}
<div class="config-info-item">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="13"
height="13"
>
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
Updated{#if selectedConfig.info.updatedBy}&nbsp;by
<strong>{selectedConfig.info.updatedBy}</strong
>{/if}{#if selectedConfig.info.lastUpdated}&nbsp;on
{formatDate(selectedConfig.info.lastUpdated)}{/if}
</div>
{/if}
</div>
{/if}
</div>
{/key}
</div>
</div>
</div>
</div>
{/if}
</div>
{/if}