Setup unifi wlans
This commit is contained in:
@@ -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} by
|
||||
<strong>{selectedConfig.info.enteredBy}</strong
|
||||
>{/if}{#if selectedConfig.info.dateEntered} 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} by
|
||||
<strong>{selectedConfig.info.updatedBy}</strong
|
||||
>{/if}{#if selectedConfig.info.lastUpdated} on
|
||||
{formatDate(selectedConfig.info.lastUpdated)}{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user