45b968b77d
HTML: - Add type="button" to all remaining buttons (nav drawer close, menu btn, theme toggle, notif bell, right drawer close, tab buttons, sidebar toggle, alert close x4, code copy, tab bar buttons x4, detail panel open, modal close x2, keyboard shortcuts close) - Add aria-label="Search commands" to command palette input - Notification panel close(true): restore focus to bell on Escape - Generic dropdowns: add Escape key handler with trigger focus restore CSS: - Add a:focus-visible global focus ring - Add .lt-nav-dropdown-menu li a:focus-visible - Add .lt-markdown a:focus-visible - Fix dead .lt-typeahead-option selector → .lt-typeahead-item with :hover, .is-focused, :focus-visible for light theme Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1990 lines
108 KiB
HTML
1990 lines
108 KiB
HTML
<!DOCTYPE html>
|
||
<!--
|
||
LOTUSGUILD TERMINAL DESIGN SYSTEM v1.2 — base.html
|
||
Reference template showing every component and layout pattern.
|
||
|
||
This file is a STATIC DEMO. Framework-specific wiring is in:
|
||
php/ → PHP / Tinker Tickets
|
||
python/ → Flask / Jinja2 / GANDALF
|
||
node/ → Express / EJS / PULSE
|
||
|
||
To build a new app, copy one of the framework skeletons from those
|
||
sub-directories and reference base.css + base.js.
|
||
-->
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||
<meta name="theme-color" content="#030508">
|
||
<meta name="mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<title>MY APP — LotusGuild</title>
|
||
<meta name="description" content="LotusGuild infrastructure application">
|
||
<meta name="robots" content="noindex, nofollow">
|
||
|
||
<!-- =========================================================
|
||
Security headers are set server-side. CSP nonce is injected
|
||
by SecurityHeadersMiddleware (PHP) / helmet (Node) / Flask.
|
||
All <script> tags need: nonce="NONCE_PLACEHOLDER"
|
||
========================================================= -->
|
||
|
||
<!-- Monospace font: JetBrains Mono -->
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
|
||
|
||
<!-- Hacker template design system CSS -->
|
||
<link rel="stylesheet" href="/web_template/base.css">
|
||
<!--
|
||
App-specific CSS (override or extend after base.css):
|
||
<link rel="stylesheet" href="/assets/css/app.css">
|
||
-->
|
||
|
||
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
|
||
</head>
|
||
<body>
|
||
|
||
<a class="lt-skip-link" href="#main-content">Skip to main content</a>
|
||
|
||
<!-- ===========================================================
|
||
BOOT SEQUENCE OVERLAY
|
||
Displays once per session. Remove if not desired.
|
||
data-app-name → used in the boot text banner.
|
||
=========================================================== -->
|
||
<div id="lt-boot" class="lt-boot-overlay" data-app-name="MY APP" style="display:none">
|
||
<pre id="lt-boot-text" class="lt-boot-text"></pre>
|
||
</div>
|
||
|
||
<!-- ===========================================================
|
||
HEADER
|
||
=========================================================== -->
|
||
<!-- ===========================================================
|
||
MOBILE NAV DRAWER (hidden on desktop, slides in on mobile)
|
||
=========================================================== -->
|
||
<div id="lt-nav-drawer" class="lt-nav-drawer" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Navigation menu">
|
||
<div class="lt-nav-drawer-header">
|
||
<span class="lt-brand-title">MY APP</span>
|
||
<button type="button" class="lt-nav-drawer-close" id="lt-nav-drawer-close" aria-label="Close menu">✕</button>
|
||
</div>
|
||
<nav class="lt-nav-drawer-links" aria-label="Mobile navigation">
|
||
<a href="/" class="lt-nav-drawer-link active" aria-current="page">Dashboard</a>
|
||
<a href="/tickets" class="lt-nav-drawer-link">Tickets</a>
|
||
<a href="/workers" class="lt-nav-drawer-link">Workers</a>
|
||
<div class="lt-nav-drawer-section">Admin</div>
|
||
<a href="/admin/templates" class="lt-nav-drawer-link lt-nav-drawer-link--indent">Templates</a>
|
||
<a href="/admin/workflow" class="lt-nav-drawer-link lt-nav-drawer-link--indent">Workflow</a>
|
||
<a href="/admin/audit-log" class="lt-nav-drawer-link lt-nav-drawer-link--indent">Audit Log</a>
|
||
<a href="/admin/api-keys" class="lt-nav-drawer-link lt-nav-drawer-link--indent">API Keys</a>
|
||
</nav>
|
||
</div>
|
||
<div id="lt-nav-overlay" class="lt-nav-drawer-overlay"></div>
|
||
|
||
<header class="lt-header">
|
||
<div class="lt-header-left">
|
||
|
||
<!-- Hamburger — visible only on tablet/mobile (CSS hides on desktop) -->
|
||
<button type="button" class="lt-menu-btn" id="lt-menu-btn" aria-label="Open navigation menu" aria-expanded="false" aria-controls="lt-nav-drawer">
|
||
<span></span><span></span><span></span>
|
||
</button>
|
||
|
||
<!-- Brand -->
|
||
<div class="lt-brand">
|
||
<span class="lt-brand-title lt-glitch" data-text="MY APP">MY APP</span>
|
||
<span class="lt-brand-subtitle">LotusGuild Infrastructure</span>
|
||
</div>
|
||
|
||
<!-- Horizontal nav links (desktop) -->
|
||
<nav class="lt-nav" aria-label="Main navigation">
|
||
<a href="/" class="lt-nav-link active">Dashboard</a>
|
||
<a href="/tickets" class="lt-nav-link">Tickets</a>
|
||
<a href="/workers" class="lt-nav-link">Workers</a>
|
||
|
||
<!-- Dropdown example (admin menu) -->
|
||
<div class="lt-nav-dropdown">
|
||
<a href="#" class="lt-nav-link">Admin ▾</a>
|
||
<ul class="lt-nav-dropdown-menu">
|
||
<li><a href="/admin/templates">Templates</a></li>
|
||
<li><a href="/admin/workflow">Workflow</a></li>
|
||
<li><a href="/admin/audit-log">Audit Log</a></li>
|
||
<li><a href="/admin/api-keys">API Keys</a></li>
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="lt-header-right">
|
||
<!-- WebSocket status indicator -->
|
||
<div class="lt-ws-status" id="lt-ws-indicator" data-state="disconnected" aria-label="WebSocket status">
|
||
<span class="lt-dot"></span><span>Offline</span>
|
||
</div>
|
||
<!-- Theme toggle -->
|
||
<button type="button" class="lt-theme-btn" id="lt-theme-btn" aria-label="Switch to light mode" title="Switch to light mode">☀</button>
|
||
<!-- Notifications with badge + dropdown -->
|
||
<div class="lt-notif-dropdown-wrap" id="lt-notif-bell">
|
||
<button type="button" class="lt-btn lt-btn-sm lt-notif-bell-btn" id="lt-notif-bell-btn" aria-label="Open notifications" aria-expanded="false" aria-haspopup="true" style="padding:0 0.6rem;">🔔</button>
|
||
<div class="lt-notif-panel" id="lt-notif-panel" role="region" aria-label="Notifications" aria-hidden="true">
|
||
<div class="lt-notif-panel-header">
|
||
<span>Notifications</span>
|
||
<button type="button" class="lt-notif-panel-clear" id="lt-notif-clear-all">Mark all read</button>
|
||
</div>
|
||
<div class="lt-notif-panel-list">
|
||
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0" aria-label="Unread: P1 alert: storage link-down, 5 min ago. Press Enter to dismiss.">
|
||
<span class="lt-notif-dot" aria-hidden="true"></span>
|
||
<div class="lt-notif-item-body">
|
||
<div class="lt-notif-item-title">P1 alert: storage link-down</div>
|
||
<div class="lt-notif-item-time">5 min ago</div>
|
||
</div>
|
||
</div>
|
||
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0" aria-label="Unread: Worker node-03 reconnected, 12 min ago. Press Enter to dismiss.">
|
||
<span class="lt-notif-dot" aria-hidden="true"></span>
|
||
<div class="lt-notif-item-body">
|
||
<div class="lt-notif-item-title">Worker node-03 reconnected</div>
|
||
<div class="lt-notif-item-time">12 min ago</div>
|
||
</div>
|
||
</div>
|
||
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0" aria-label="Unread: Export CSV complete — 42 rows, 1 hr ago. Press Enter to dismiss.">
|
||
<span class="lt-notif-dot" aria-hidden="true"></span>
|
||
<div class="lt-notif-item-body">
|
||
<div class="lt-notif-item-title">Export CSV complete — 42 rows</div>
|
||
<div class="lt-notif-item-time">1 hr ago</div>
|
||
</div>
|
||
</div>
|
||
<div class="lt-notif-item" role="button" tabindex="0" aria-label="Scheduled maintenance completed, 3 hr ago. Press Enter to dismiss.">
|
||
<span class="lt-notif-dot lt-notif-dot--read" aria-hidden="true"></span>
|
||
<div class="lt-notif-item-body">
|
||
<div class="lt-notif-item-title">Scheduled maintenance completed</div>
|
||
<div class="lt-notif-item-time">3 hr ago</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="lt-notif-panel-footer">
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-ghost" style="width:100%;font-size:0.72rem">View all notifications</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Current user -->
|
||
<span class="lt-header-user">operator</span>
|
||
<!-- Admin badge (only show for admins) -->
|
||
<span class="lt-badge-admin">admin</span>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Right-side detail drawer -->
|
||
<div id="lt-detail-drawer" class="lt-drawer-right" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Detail panel" data-overlay="lt-detail-overlay">
|
||
<div class="lt-drawer-right-header">
|
||
<span class="lt-drawer-right-title">// TICKET DETAIL</span>
|
||
<button type="button" class="lt-drawer-right-close" data-drawer-close aria-label="Close detail panel">✕</button>
|
||
</div>
|
||
<div class="lt-drawer-right-body">
|
||
<!-- Read-only meta row -->
|
||
<div class="lt-kv-grid" style="margin-bottom:1rem">
|
||
<div class="lt-kv-row"><span class="lt-kv-label">ID</span><span class="lt-kv-value lt-text-cyan">#123456789</span></div>
|
||
<div class="lt-kv-row"><span class="lt-kv-label">Created</span><span class="lt-kv-value lt-text-muted">2026-03-10 09:14 UTC</span></div>
|
||
</div>
|
||
|
||
<!-- Editable fields -->
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="td-title">Title</label>
|
||
<input id="td-title" class="lt-input" type="text" value="Storage array link-down on compute-storage-01">
|
||
</div>
|
||
<div class="lt-drawer-form-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem">
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="td-status">Status</label>
|
||
<select id="td-status" class="lt-select">
|
||
<option selected>Open</option>
|
||
<option>In Progress</option>
|
||
<option>Pending</option>
|
||
<option>Closed</option>
|
||
</select>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="td-priority">Priority</label>
|
||
<select id="td-priority" class="lt-select">
|
||
<option selected>P1 Critical</option>
|
||
<option>P2 High</option>
|
||
<option>P3 Medium</option>
|
||
<option>P4 Low</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="td-assignee">Assignee</label>
|
||
<select id="td-assignee" class="lt-select">
|
||
<option>Unassigned</option>
|
||
<option selected>jdoe</option>
|
||
<option>operator</option>
|
||
<option>admin</option>
|
||
</select>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="td-desc">Description</label>
|
||
<textarea id="td-desc" class="lt-input lt-textarea" rows="3" style="resize:vertical">Storage array link-down on compute-storage-01. Affects prod write path. Investigate RAID controller firmware.</textarea>
|
||
</div>
|
||
|
||
<!-- Add a comment -->
|
||
<div class="lt-divider-label" style="margin:1rem 0 0.75rem">Add Comment</div>
|
||
<div class="lt-form-group" style="margin-bottom:0.5rem">
|
||
<textarea id="td-comment" class="lt-input lt-textarea" rows="2" placeholder="Leave a comment…" style="resize:vertical" aria-label="Add comment"></textarea>
|
||
</div>
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="
|
||
const c=document.getElementById('td-comment');
|
||
if(c.value.trim()){lt.toast.success('Comment posted');c.value='';}
|
||
else lt.toast.warning('Comment is empty');
|
||
">Post Comment</button>
|
||
|
||
<!-- Activity timeline -->
|
||
<div class="lt-divider-label" style="margin:1rem 0 0.75rem">Activity</div>
|
||
<div class="lt-timeline">
|
||
<div class="lt-timeline-item lt-timeline-item--orange">
|
||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> assigned ticket<span class="lt-timeline-time">2h ago</span></div>
|
||
<div class="lt-timeline-body">Assigned to self, escalated to P1.</div>
|
||
</div>
|
||
<div class="lt-timeline-item">
|
||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">sysbot</span> auto-created<span class="lt-timeline-time">3h ago</span></div>
|
||
<div class="lt-timeline-body">Alert triggered: <code>node_network_up = 0</code></div>
|
||
</div>
|
||
<div class="lt-timeline-item lt-timeline-item--dim">
|
||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">monitor</span> detected<span class="lt-timeline-time">3h ago</span></div>
|
||
<div class="lt-timeline-body">NIC link-down detected on large1:enp35s0.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="lt-drawer-right-footer">
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.rightDrawer.close('lt-detail-drawer')">Cancel</button>
|
||
<button type="button" class="lt-btn lt-btn-primary lt-btn-sm" onclick="lt.toast.success('Ticket #123456789 updated')">Save Changes</button>
|
||
</div>
|
||
</div>
|
||
<div id="lt-detail-overlay" class="lt-drawer-right-overlay"></div>
|
||
|
||
<!-- ===========================================================
|
||
MAIN CONTENT AREA
|
||
=========================================================== -->
|
||
<main class="lt-main lt-container" id="main-content">
|
||
|
||
<!-- Page title bar -->
|
||
<div class="lt-page-header">
|
||
<h1 class="lt-page-title">Dashboard</h1>
|
||
<div class="lt-btn-group">
|
||
<a href="/create" class="lt-btn lt-btn-primary">New Ticket</a>
|
||
<button type="button" class="lt-btn lt-btn-sm" data-modal-open="export-modal">Export</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==========================================================
|
||
STATS WIDGETS
|
||
data-filter-key / data-filter-val → wired by lt.statsFilter
|
||
========================================================== -->
|
||
<div class="lt-stats-grid">
|
||
<div class="lt-stat-card active" role="button" tabindex="0" data-filter-key="status" data-filter-val="Open" aria-label="Open tickets: 42">
|
||
<span class="lt-stat-icon" aria-hidden="true">📋</span>
|
||
<div class="lt-stat-info">
|
||
<span class="lt-stat-value">42</span>
|
||
<span class="lt-stat-label">Open</span>
|
||
</div>
|
||
</div>
|
||
<div class="lt-stat-card" role="button" tabindex="0" data-filter-key="priority" data-filter-val="1" aria-label="Critical tickets: 3">
|
||
<span class="lt-stat-icon" aria-hidden="true">🔴</span>
|
||
<div class="lt-stat-info">
|
||
<span class="lt-stat-value">3</span>
|
||
<span class="lt-stat-label">Critical</span>
|
||
</div>
|
||
</div>
|
||
<div class="lt-stat-card" role="button" tabindex="0" data-filter-key="assigned_to" data-filter-val="0" aria-label="Unassigned tickets: 11">
|
||
<span class="lt-stat-icon" aria-hidden="true">👤</span>
|
||
<div class="lt-stat-info">
|
||
<span class="lt-stat-value">11</span>
|
||
<span class="lt-stat-label">Unassigned</span>
|
||
</div>
|
||
</div>
|
||
<div class="lt-stat-card" role="button" tabindex="0" data-filter-key="created" data-filter-val="today" aria-label="Today's tickets: 7">
|
||
<span class="lt-stat-icon" aria-hidden="true">📅</span>
|
||
<div class="lt-stat-info">
|
||
<span class="lt-stat-value">7</span>
|
||
<span class="lt-stat-label">Today</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==========================================================
|
||
TAB NAVIGATION
|
||
========================================================== -->
|
||
<div class="lt-tabs" role="tablist" aria-label="Main views">
|
||
<button type="button" class="lt-tab active" role="tab" data-tab="tab-table" aria-selected="true" aria-controls="tab-table" id="tab-btn-table">Table View</button>
|
||
<button type="button" class="lt-tab" role="tab" data-tab="tab-kanban" aria-selected="false" aria-controls="tab-kanban" id="tab-btn-kanban">Kanban</button>
|
||
<button type="button" class="lt-tab" role="tab" data-tab="tab-workers" aria-selected="false" aria-controls="tab-workers" id="tab-btn-workers">Workers</button>
|
||
</div>
|
||
|
||
<!-- ==========================================================
|
||
SIDEBAR + CONTENT LAYOUT
|
||
========================================================== -->
|
||
<div class="lt-layout">
|
||
|
||
<!-- Sidebar filter panel -->
|
||
<aside class="lt-sidebar" id="lt-sidebar">
|
||
<div class="lt-sidebar-header">
|
||
Filters
|
||
<button type="button" class="lt-sidebar-toggle" data-sidebar-toggle="lt-sidebar"
|
||
aria-label="Collapse filters">◀</button>
|
||
</div>
|
||
<div class="lt-sidebar-body">
|
||
|
||
<fieldset class="lt-filter-group">
|
||
<legend class="lt-filter-label">Status</legend>
|
||
<label class="lt-filter-option">
|
||
<input type="checkbox" class="lt-checkbox" checked> Open
|
||
</label>
|
||
<label class="lt-filter-option">
|
||
<input type="checkbox" class="lt-checkbox"> Pending
|
||
</label>
|
||
<label class="lt-filter-option">
|
||
<input type="checkbox" class="lt-checkbox"> In Progress
|
||
</label>
|
||
<label class="lt-filter-option">
|
||
<input type="checkbox" class="lt-checkbox"> Closed
|
||
</label>
|
||
</fieldset>
|
||
|
||
<fieldset class="lt-filter-group">
|
||
<legend class="lt-filter-label">Priority</legend>
|
||
<label class="lt-filter-option">
|
||
<input type="checkbox" class="lt-checkbox"> P1 Critical
|
||
</label>
|
||
<label class="lt-filter-option">
|
||
<input type="checkbox" class="lt-checkbox"> P2 High
|
||
</label>
|
||
<label class="lt-filter-option">
|
||
<input type="checkbox" class="lt-checkbox"> P3 Medium
|
||
</label>
|
||
</fieldset>
|
||
|
||
<div class="lt-filter-group">
|
||
<label class="lt-filter-label" for="filter-assigned-to">Assigned To</label>
|
||
<div class="lt-form-group">
|
||
<select class="lt-select lt-btn-sm" id="filter-assigned-to">
|
||
<option value="">All users</option>
|
||
<option>operator</option>
|
||
<option>admin</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lt-btn-group">
|
||
<button type="button" class="lt-btn lt-btn-sm">Apply</button>
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-ghost">Reset</button>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Main content -->
|
||
<div class="lt-content">
|
||
|
||
<!-- Toolbar: search + actions -->
|
||
<div class="lt-toolbar">
|
||
<div class="lt-toolbar-left">
|
||
<div class="lt-search">
|
||
<input type="search" class="lt-input lt-search-input" id="ticket-search"
|
||
placeholder="Search tickets..." aria-label="Search" autocomplete="off">
|
||
</div>
|
||
<!-- Advanced filter dropdown -->
|
||
<div class="lt-dropdown-wrap" id="adv-filter-wrap">
|
||
<button type="button" class="lt-btn lt-btn-sm lt-dropdown-trigger" id="adv-filter-btn" aria-expanded="false" aria-haspopup="true">Advanced ▾</button>
|
||
<div class="lt-dropdown-panel" id="adv-filter-panel" aria-hidden="true">
|
||
<div style="padding:0.75rem;display:grid;gap:0.5rem;width:clamp(200px,60vw,260px)">
|
||
<div class="lt-form-group" style="margin:0">
|
||
<label class="lt-label" style="font-size:0.75rem" for="adv-filter-status">Status</label>
|
||
<select id="adv-filter-status" class="lt-select" style="font-size:0.8rem">
|
||
<option value="">All</option>
|
||
<option>Open</option>
|
||
<option>In Progress</option>
|
||
<option>Pending</option>
|
||
<option>Closed</option>
|
||
</select>
|
||
</div>
|
||
<div class="lt-form-group" style="margin:0">
|
||
<label class="lt-label" style="font-size:0.75rem" for="adv-filter-priority">Priority</label>
|
||
<select id="adv-filter-priority" class="lt-select" style="font-size:0.8rem">
|
||
<option value="">All</option>
|
||
<option>P1 Critical</option>
|
||
<option>P2 High</option>
|
||
<option>P3 Medium</option>
|
||
<option>P4 Low</option>
|
||
</select>
|
||
</div>
|
||
<div class="lt-form-group" style="margin:0">
|
||
<label class="lt-label" style="font-size:0.75rem" for="adv-filter-assignee">Assignee</label>
|
||
<select id="adv-filter-assignee" class="lt-select" style="font-size:0.8rem">
|
||
<option value="">Anyone</option>
|
||
<option>Unassigned</option>
|
||
<option>jdoe</option>
|
||
<option>operator</option>
|
||
<option>admin</option>
|
||
</select>
|
||
</div>
|
||
<div style="display:flex;gap:0.5rem;margin-top:0.25rem">
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-primary" style="flex:1" onclick="lt.toast.info('Filters applied');document.getElementById('adv-filter-panel').setAttribute('aria-hidden','true');document.getElementById('adv-filter-btn').setAttribute('aria-expanded','false')">Apply</button>
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-ghost" onclick="lt.toast.info('Filters cleared')">Reset</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="lt-toolbar-right">
|
||
<span class="lt-text-muted lt-text-xs lt-hide-xs" id="ticket-result-count">42 results</span>
|
||
<!-- Bulk actions dropdown -->
|
||
<div class="lt-dropdown-wrap" id="bulk-action-wrap">
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-ghost lt-dropdown-trigger" id="bulk-action-btn" aria-expanded="false" aria-haspopup="true">Bulk Actions ▾</button>
|
||
<div class="lt-dropdown-panel lt-dropdown-panel--right" id="bulk-action-panel" aria-hidden="true">
|
||
<button type="button" class="lt-dropdown-item" onclick="lt.toast.success('Closed selected tickets');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">✓ Close Selected</button>
|
||
<button type="button" class="lt-dropdown-item" onclick="lt.toast.info('Reassign dialog…');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">↩ Reassign…</button>
|
||
<button type="button" class="lt-dropdown-item" onclick="lt.toast.info('Exporting selected…');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">⤓ Export Selected</button>
|
||
<div class="lt-dropdown-divider"></div>
|
||
<button type="button" class="lt-dropdown-item lt-dropdown-item--danger" onclick="lt.toast.error('Deleted selected');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">🗑 Delete Selected</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================================================
|
||
TAB PANEL: TABLE VIEW
|
||
================================================== -->
|
||
<div id="tab-table" class="lt-tab-panel active" role="tabpanel" aria-labelledby="tab-btn-table">
|
||
|
||
<!-- Outer ASCII frame wrapping the table -->
|
||
<div class="lt-frame">
|
||
<span class="lt-frame-bl">╚</span>
|
||
<span class="lt-frame-br">╝</span>
|
||
|
||
<div class="lt-section-header">Ticket Queue</div>
|
||
|
||
<div class="lt-table-wrap">
|
||
<table class="lt-table lt-table-responsive" id="ticket-table" aria-label="Ticket queue">
|
||
<caption class="lt-sr-only">Ticket queue — sorted by priority</caption>
|
||
<thead>
|
||
<tr>
|
||
<th scope="col"><input type="checkbox" class="lt-checkbox" aria-label="Select all"></th>
|
||
<th scope="col" data-sort-key="id" aria-sort="none">ID</th>
|
||
<th scope="col" data-sort-key="priority" aria-sort="none">Priority</th>
|
||
<th scope="col" data-sort-key="title" aria-sort="none">Title</th>
|
||
<th scope="col" data-sort-key="status" aria-sort="none">Status</th>
|
||
<th scope="col" data-sort-key="assignee" aria-sort="none">Assignee</th>
|
||
<th scope="col" data-sort-key="created" aria-sort="none">Created</th>
|
||
<th scope="col">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
|
||
<!-- P1 Critical row -->
|
||
<tr class="lt-row-p1 lt-row-critical">
|
||
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #123456789"></td>
|
||
<td data-label="ID"><a href="/ticket/123456789">#123456789</a></td>
|
||
<td data-label="Priority"><span class="lt-p1">P1 Critical</span></td>
|
||
<td data-label="Title">Storage array link-down on compute-storage-01</td>
|
||
<td data-label="Status"><span class="lt-status lt-status-open">Open</span></td>
|
||
<td data-label="Assignee" class="lt-text-muted">Unassigned</td>
|
||
<td data-label="Created" class="lt-text-xs lt-text-muted">5m ago</td>
|
||
<td data-label="Actions">
|
||
<div class="lt-btn-group">
|
||
<a href="/ticket/123456789" class="lt-btn lt-btn-sm">View</a>
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger">Close</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- P2 High row -->
|
||
<tr class="lt-row-p2 lt-row-warning">
|
||
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #987654321"></td>
|
||
<td data-label="ID"><a href="/ticket/987654321">#987654321</a></td>
|
||
<td data-label="Priority"><span class="lt-p2">P2 High</span></td>
|
||
<td data-label="Title">Switch port flapping on USW-Pro-24</td>
|
||
<td data-label="Status"><span class="lt-status lt-status-in-progress">In Progress</span></td>
|
||
<td data-label="Assignee">operator</td>
|
||
<td data-label="Created" class="lt-text-xs lt-text-muted">2h ago</td>
|
||
<td data-label="Actions">
|
||
<div class="lt-btn-group">
|
||
<a href="/ticket/987654321" class="lt-btn lt-btn-sm">View</a>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- P3 Medium row -->
|
||
<tr class="lt-row-p3">
|
||
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #111222333"></td>
|
||
<td data-label="ID"><a href="/ticket/111222333">#111222333</a></td>
|
||
<td data-label="Priority"><span class="lt-p3">P3 Med</span></td>
|
||
<td data-label="Title">Scheduled maintenance: replace SFP+ on large1</td>
|
||
<td data-label="Status"><span class="lt-status lt-status-pending">Pending</span></td>
|
||
<td data-label="Assignee">admin</td>
|
||
<td data-label="Created" class="lt-text-xs lt-text-muted">1d ago</td>
|
||
<td data-label="Actions">
|
||
<div class="lt-btn-group">
|
||
<a href="/ticket/111222333" class="lt-btn lt-btn-sm">View</a>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- P4 closed -->
|
||
<tr class="lt-row-p4">
|
||
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #444555666"></td>
|
||
<td data-label="ID"><a href="/ticket/444555666">#444555666</a></td>
|
||
<td data-label="Priority"><span class="lt-p4">P4 Low</span></td>
|
||
<td data-label="Title">Update SSL cert on wiki.lotusguild.org</td>
|
||
<td data-label="Status"><span class="lt-status lt-status-closed">Closed</span></td>
|
||
<td data-label="Assignee">operator</td>
|
||
<td data-label="Created" class="lt-text-xs lt-text-muted">3d ago</td>
|
||
<td data-label="Actions">
|
||
<div class="lt-btn-group">
|
||
<a href="/ticket/444555666" class="lt-btn lt-btn-sm">View</a>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div><!-- /.lt-frame -->
|
||
|
||
</div><!-- /#tab-table -->
|
||
|
||
<!-- ==================================================
|
||
TAB PANEL: KANBAN
|
||
================================================== -->
|
||
<div id="tab-kanban" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab-btn-kanban">
|
||
<div class="lt-grid-4">
|
||
|
||
<!-- Kanban column: Open -->
|
||
<div class="lt-frame">
|
||
<span class="lt-frame-bl">╚</span>
|
||
<span class="lt-frame-br">╝</span>
|
||
<div class="lt-section-header">Open</div>
|
||
<div class="lt-section-body" id="kanban-col-open" style="min-height:60px">
|
||
|
||
<div class="lt-card lt-mb-md lt-row-p1" role="article" aria-label="P1 — Storage array link-down, 5m ago, Unassigned">
|
||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||
<span class="lt-p1" aria-hidden="true">P1</span>
|
||
<span class="lt-dot lt-dot-up" aria-hidden="true"></span>
|
||
</div>
|
||
<div class="lt-text-sm">Storage array link-down</div>
|
||
<div class="lt-text-xs lt-text-muted lt-mt-sm">5m ago · Unassigned</div>
|
||
</div>
|
||
|
||
<div class="lt-card lt-row-p3" role="article" aria-label="P3 — Update node_exporter on micro1, 1d ago, operator">
|
||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||
<span class="lt-p3" aria-hidden="true">P3</span>
|
||
<span class="lt-dot lt-dot-up" aria-hidden="true"></span>
|
||
</div>
|
||
<div class="lt-text-sm">Update node_exporter on micro1</div>
|
||
<div class="lt-text-xs lt-text-muted lt-mt-sm">1d ago · operator</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kanban column: Pending -->
|
||
<div class="lt-frame">
|
||
<span class="lt-frame-bl">╚</span>
|
||
<span class="lt-frame-br">╝</span>
|
||
<div class="lt-section-header">Pending</div>
|
||
<div class="lt-section-body" id="kanban-col-pending" style="min-height:60px">
|
||
<div class="lt-card lt-row-p2" role="article" aria-label="P2 — Scheduled maintenance window, 2d ago, admin">
|
||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||
<span class="lt-p2" aria-hidden="true">P2</span>
|
||
<span class="lt-dot lt-dot-warn" aria-hidden="true"></span>
|
||
</div>
|
||
<div class="lt-text-sm">Scheduled maintenance window</div>
|
||
<div class="lt-text-xs lt-text-muted lt-mt-sm">2d ago · admin</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kanban column: In Progress -->
|
||
<div class="lt-frame">
|
||
<span class="lt-frame-bl">╚</span>
|
||
<span class="lt-frame-br">╝</span>
|
||
<div class="lt-section-header">In Progress</div>
|
||
<div class="lt-section-body" id="kanban-col-inprogress" style="min-height:60px">
|
||
<div class="lt-card lt-row-p2 lt-item-running" role="article" aria-label="P2 — Switch port flapping on USW-Pro, 2h ago, operator">
|
||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||
<span class="lt-p2" aria-hidden="true">P2</span>
|
||
<span class="lt-dot lt-dot-warn" aria-hidden="true"></span>
|
||
</div>
|
||
<div class="lt-text-sm">Switch port flapping on USW-Pro</div>
|
||
<div class="lt-text-xs lt-text-muted lt-mt-sm">2h ago · operator</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kanban column: Closed -->
|
||
<div class="lt-frame">
|
||
<span class="lt-frame-bl">╚</span>
|
||
<span class="lt-frame-br">╝</span>
|
||
<div class="lt-section-header">Closed</div>
|
||
<div class="lt-section-body" id="kanban-col-closed" style="min-height:60px">
|
||
<div class="lt-card lt-row-p4" style="opacity:0.6" role="article" aria-label="P4 — Update SSL cert on wiki, 3d ago, operator">
|
||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||
<span class="lt-p4" aria-hidden="true">P4</span>
|
||
<span class="lt-dot lt-dot-idle" aria-hidden="true"></span>
|
||
</div>
|
||
<div class="lt-text-sm">Update SSL cert on wiki</div>
|
||
<div class="lt-text-xs lt-text-muted lt-mt-sm">3d ago · operator</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /.lt-grid-4 -->
|
||
</div><!-- /#tab-kanban -->
|
||
|
||
<!-- ==================================================
|
||
TAB PANEL: WORKERS
|
||
================================================== -->
|
||
<div id="tab-workers" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab-btn-workers">
|
||
<div class="lt-grid-3">
|
||
|
||
<div class="lt-card">
|
||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||
<span class="lt-text-amber lt-text-upper">pulse-worker-01</span>
|
||
<span class="lt-status lt-status-online">Online</span>
|
||
</div>
|
||
<div class="lt-data-table-wrapper">
|
||
<table class="lt-data-table" aria-label="pulse-worker-01 metrics">
|
||
<tbody>
|
||
<tr><th scope="row" class="lt-text-muted">CPU</th> <td>12%</td></tr>
|
||
<tr><th scope="row" class="lt-text-muted">Memory</th> <td>2.1 GB / 8 GB</td></tr>
|
||
<tr><th scope="row" class="lt-text-muted">Load</th> <td>0.42 / 0.51 / 0.48</td></tr>
|
||
<tr><th scope="row" class="lt-text-muted">Uptime</th> <td>14d 6h</td></tr>
|
||
<tr><th scope="row" class="lt-text-muted">Tasks</th> <td>2 / 5</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lt-card">
|
||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||
<span class="lt-text-amber lt-text-upper">pulse-worker-02</span>
|
||
<span class="lt-status lt-status-offline">Offline</span>
|
||
</div>
|
||
<div class="lt-msg lt-msg-warning">Last seen 14m ago</div>
|
||
</div>
|
||
|
||
<div class="lt-card">
|
||
<div class="lt-empty">No more workers registered.</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div><!-- /#tab-workers -->
|
||
|
||
</div><!-- /.lt-content -->
|
||
</div><!-- /.lt-layout -->
|
||
|
||
|
||
<!-- ==========================================================
|
||
COMPONENT SHOWCASE (remove in production)
|
||
========================================================== -->
|
||
<div class="lt-divider"></div>
|
||
|
||
<!-- Inner frame + subsection example -->
|
||
<div class="lt-frame">
|
||
<span class="lt-frame-bl">╚</span>
|
||
<span class="lt-frame-br">╝</span>
|
||
<div class="lt-section-header">Component Reference</div>
|
||
|
||
<div class="lt-frame-inner">
|
||
<div class="lt-subsection-header">Buttons</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-btn-group">
|
||
<button type="button" class="lt-btn">Default</button>
|
||
<button type="button" class="lt-btn lt-btn-primary">Primary</button>
|
||
<button type="button" class="lt-btn lt-btn-danger">Danger</button>
|
||
<button type="button" class="lt-btn lt-btn-sm">Small</button>
|
||
<button type="button" class="lt-btn lt-btn-ghost">Ghost</button>
|
||
<button type="button" class="lt-btn" disabled>Disabled</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lt-subsection-header">Status Badges</div>
|
||
<div class="lt-section-body lt-flex lt-flex-wrap lt-gap-md">
|
||
<span class="lt-status lt-status-open">Open</span>
|
||
<span class="lt-status lt-status-pending">Pending</span>
|
||
<span class="lt-status lt-status-in-progress">In Progress</span>
|
||
<span class="lt-status lt-status-closed">Closed</span>
|
||
<span class="lt-status lt-status-online">Online</span>
|
||
<span class="lt-status lt-status-offline">Offline</span>
|
||
<span class="lt-status lt-status-running">Running</span>
|
||
<span class="lt-status lt-status-completed">Completed</span>
|
||
<span class="lt-status lt-status-failed">Failed</span>
|
||
</div>
|
||
|
||
<div class="lt-subsection-header">Priority Badges</div>
|
||
<div class="lt-section-body lt-flex lt-flex-wrap lt-gap-md">
|
||
<span class="lt-priority lt-p1">P1 Critical</span>
|
||
<span class="lt-priority lt-p2">P2 High</span>
|
||
<span class="lt-priority lt-p3">P3 Med</span>
|
||
<span class="lt-priority lt-p4">P4 Low</span>
|
||
<span class="lt-priority lt-p5">P5 Min</span>
|
||
</div>
|
||
|
||
<div class="lt-subsection-header">Chips & Badges</div>
|
||
<div class="lt-section-body lt-flex lt-flex-wrap lt-gap-sm">
|
||
<span class="lt-chip lt-chip-ok">OK</span>
|
||
<span class="lt-chip lt-chip-warn">Warning</span>
|
||
<span class="lt-chip lt-chip-critical">Critical</span>
|
||
<span class="lt-chip lt-chip-info">Info</span>
|
||
<span class="lt-badge lt-badge-green">v1.0</span>
|
||
<span class="lt-badge lt-badge-amber">Beta</span>
|
||
<span class="lt-badge lt-badge-red">Deprecated</span>
|
||
<span class="lt-badge-admin">admin</span>
|
||
</div>
|
||
|
||
<div class="lt-subsection-header">Status Dots</div>
|
||
<div class="lt-section-body lt-flex lt-gap-md">
|
||
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-up"></span> Up</span>
|
||
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-down"></span> Down</span>
|
||
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-warn"></span> Degraded</span>
|
||
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-idle"></span> Idle</span>
|
||
</div>
|
||
|
||
<div class="lt-subsection-header">Inline Messages</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-msg lt-msg-error">Database connection refused on 10.10.10.50</div>
|
||
<div class="lt-msg lt-msg-success">Ticket #123456789 updated successfully</div>
|
||
<div class="lt-msg lt-msg-warning">Rate limit: 80% consumed (40/50 req/min)</div>
|
||
<div class="lt-msg lt-msg-info">Auto-refresh active — updates every 30s</div>
|
||
</div>
|
||
|
||
<div class="lt-subsection-header">Forms</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid-2">
|
||
<div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label lt-label-required" for="eg-title">Ticket Title</label>
|
||
<input id="eg-title" type="text" class="lt-input" placeholder="Describe the issue">
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="eg-priority">Priority</label>
|
||
<select id="eg-priority" class="lt-select">
|
||
<option>P1 — Critical</option>
|
||
<option>P2 — High</option>
|
||
<option selected>P3 — Medium</option>
|
||
<option>P4 — Low</option>
|
||
<option>P5 — Minimal</option>
|
||
</select>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="eg-desc">Description</label>
|
||
<textarea id="eg-desc" class="lt-textarea" placeholder="Markdown supported..."></textarea>
|
||
<span class="lt-form-hint">Markdown supported. Use #123456789 to link tickets.</span>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label">
|
||
<input type="checkbox" class="lt-checkbox"> Confidential ticket
|
||
</label>
|
||
</div>
|
||
<div class="lt-btn-group">
|
||
<button type="button" class="lt-btn lt-btn-primary">Submit</button>
|
||
<button type="button" class="lt-btn lt-btn-ghost">Cancel</button>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div class="lt-search lt-form-group">
|
||
<label class="lt-label" for="eg-search">Search</label>
|
||
<input id="eg-search" type="search" class="lt-input lt-search-input"
|
||
placeholder="Ctrl+K to focus" autocomplete="off">
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label">Loading state (skeleton)</label>
|
||
<div class="lt-skeleton lt-p-md" style="height:40px"></div>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label">Empty state</label>
|
||
<div class="lt-empty" style="padding:1rem">No results found</div>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label">Loading indicator</label>
|
||
<div class="lt-loading" style="padding:1rem"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lt-subsection-header">Log / Timeline Entries</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-log-entry success">
|
||
<div class="lt-log-ts">2026-03-14 09:12:33 EDT</div>
|
||
<strong>Status changed:</strong> Open → In Progress by operator
|
||
</div>
|
||
<div class="lt-log-entry warning">
|
||
<div class="lt-log-ts">2026-03-14 09:10:11 EDT</div>
|
||
<strong>Priority escalated:</strong> P3 → P1 by GANDALF auto-alert
|
||
</div>
|
||
<div class="lt-log-entry error">
|
||
<div class="lt-log-ts">2026-03-14 09:09:55 EDT</div>
|
||
<strong>Alert triggered:</strong> NIC link-down on large1:enp35s0
|
||
<div class="lt-log-output">node_exporter metric: node_network_up{interface="enp35s0"} = 0</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /.lt-frame-inner -->
|
||
</div><!-- /.lt-frame (component showcase) -->
|
||
|
||
|
||
<!-- ===========================================================
|
||
COMPONENT SHOWCASE — v1.2 ADDITIONS
|
||
=========================================================== -->
|
||
<div class="lt-frame lt-mt-lg">
|
||
<div class="lt-frame-inner">
|
||
|
||
<!-- PROGRESS BARS -->
|
||
<div class="lt-section-header">Progress Bars</div>
|
||
<div class="lt-section-body">
|
||
<div style="display:flex;flex-direction:column;gap:var(--space-md)">
|
||
<div>
|
||
<div class="lt-progress-label"><span>CPU LOAD</span><span>72%</span></div>
|
||
<div class="lt-progress" role="progressbar" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100" aria-label="CPU Load"><div class="lt-progress-bar" style="width:72%" data-width="72%"></div></div>
|
||
</div>
|
||
<div>
|
||
<div class="lt-progress-label"><span>MEMORY</span><span>45%</span></div>
|
||
<div class="lt-progress lt-progress--cyan" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" aria-label="Memory"><div class="lt-progress-bar" style="width:45%" data-width="45%"></div></div>
|
||
</div>
|
||
<div>
|
||
<div class="lt-progress-label"><span>DISK I/O</span><span>89%</span></div>
|
||
<div class="lt-progress lt-progress--red lt-progress--lg" role="progressbar" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100" aria-label="Disk I/O"><div class="lt-progress-bar" style="width:89%" data-width="89%"></div></div>
|
||
</div>
|
||
<div>
|
||
<div class="lt-progress-label"><span>UPTIME</span><span>100%</span></div>
|
||
<div class="lt-progress lt-progress--green lt-progress--striped" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" aria-label="Uptime"><div class="lt-progress-bar" style="width:100%" data-width="100%"></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- BREADCRUMBS & PAGINATION -->
|
||
<div class="lt-section-header">Breadcrumbs / Pagination</div>
|
||
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-md)">
|
||
<nav class="lt-breadcrumb" aria-label="breadcrumb">
|
||
<div class="lt-breadcrumb-item"><a href="#">ROOT</a></div>
|
||
<div class="lt-breadcrumb-sep"></div>
|
||
<div class="lt-breadcrumb-item"><a href="#">SYSTEMS</a></div>
|
||
<div class="lt-breadcrumb-sep"></div>
|
||
<div class="lt-breadcrumb-item"><a href="#">NETWORK</a></div>
|
||
<div class="lt-breadcrumb-sep"></div>
|
||
<div class="lt-breadcrumb-item active">NODE-07</div>
|
||
</nav>
|
||
<nav class="lt-pagination" id="demo-pagination" aria-label="pagination"></nav>
|
||
</div>
|
||
|
||
<!-- ACCORDION -->
|
||
<div class="lt-section-header">Accordion</div>
|
||
<div class="lt-section-body">
|
||
<div>
|
||
<div class="lt-accordion">
|
||
<button type="button" class="lt-accordion-header" aria-expanded="false" aria-controls="acc-body-1" data-accordion>
|
||
SYSTEM OVERVIEW
|
||
<svg class="lt-accordion-icon" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M1 1l4 4 4-4"/></svg>
|
||
</button>
|
||
<div class="lt-accordion-body" id="acc-body-1"><div class="lt-accordion-content">Node running at 72% CPU. 12 active processes. Last restart: 3d ago.</div></div>
|
||
</div>
|
||
<div class="lt-accordion">
|
||
<button type="button" class="lt-accordion-header" aria-expanded="false" aria-controls="acc-body-2" data-accordion>
|
||
NETWORK CONFIG
|
||
<svg class="lt-accordion-icon" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M1 1l4 4 4-4"/></svg>
|
||
</button>
|
||
<div class="lt-accordion-body" id="acc-body-2"><div class="lt-accordion-content">eth0: 10.0.0.7 — MTU 1500 — RX 4.2 GB — TX 1.1 GB</div></div>
|
||
</div>
|
||
<div class="lt-accordion">
|
||
<button type="button" class="lt-accordion-header" aria-expanded="false" aria-controls="acc-body-3" data-accordion>
|
||
FIREWALL RULES
|
||
<svg class="lt-accordion-icon" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M1 1l4 4 4-4"/></svg>
|
||
</button>
|
||
<div class="lt-accordion-body" id="acc-body-3"><div class="lt-accordion-content">22/tcp ALLOW — 80/tcp ALLOW — 443/tcp ALLOW — */* DENY</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ALERTS -->
|
||
<div class="lt-section-header">Alert Banners</div>
|
||
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-sm)">
|
||
<div class="lt-alert">
|
||
<span class="lt-alert-icon">ℹ</span>
|
||
<div class="lt-alert-body"><div class="lt-alert-title">Information</div><div class="lt-alert-msg">Scheduled maintenance window: Sunday 02:00–04:00 UTC.</div></div>
|
||
<button type="button" class="lt-alert-close" data-alert-close aria-label="Dismiss">✕</button>
|
||
</div>
|
||
<div class="lt-alert lt-alert--warning">
|
||
<span class="lt-alert-icon">⚠</span>
|
||
<div class="lt-alert-body"><div class="lt-alert-title">Warning</div><div class="lt-alert-msg">Disk usage on NODE-03 at 87%. Consider cleanup.</div></div>
|
||
<button type="button" class="lt-alert-close" data-alert-close aria-label="Dismiss">✕</button>
|
||
</div>
|
||
<div class="lt-alert lt-alert--error">
|
||
<span class="lt-alert-icon">✕</span>
|
||
<div class="lt-alert-body"><div class="lt-alert-title">Critical</div><div class="lt-alert-msg">NODE-11 unreachable. Last seen 14m ago.</div></div>
|
||
<button type="button" class="lt-alert-close" data-alert-close aria-label="Dismiss">✕</button>
|
||
</div>
|
||
<div class="lt-alert lt-alert--success">
|
||
<span class="lt-alert-icon">✓</span>
|
||
<div class="lt-alert-body"><div class="lt-alert-title">Success</div><div class="lt-alert-msg">Deployment v2.4.1 completed successfully.</div></div>
|
||
<button type="button" class="lt-alert-close" data-alert-close aria-label="Dismiss">✕</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TOGGLES, RANGE, TAGS -->
|
||
<div class="lt-section-header">Toggles / Range / Tags</div>
|
||
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-lg)">
|
||
<div style="display:flex;gap:var(--space-lg);flex-wrap:wrap">
|
||
<label class="lt-toggle">
|
||
<input type="checkbox" checked>
|
||
<div class="lt-toggle-track"><div class="lt-toggle-thumb"></div></div>
|
||
<span class="lt-toggle-label">Auto-refresh</span>
|
||
</label>
|
||
<label class="lt-toggle">
|
||
<input type="checkbox">
|
||
<div class="lt-toggle-track"><div class="lt-toggle-thumb"></div></div>
|
||
<span class="lt-toggle-label">Dark alerts</span>
|
||
</label>
|
||
<label class="lt-toggle">
|
||
<input type="checkbox" checked>
|
||
<div class="lt-toggle-track"><div class="lt-toggle-thumb"></div></div>
|
||
<span class="lt-toggle-label">Notifications</span>
|
||
</label>
|
||
</div>
|
||
<div class="lt-range-wrap" style="max-width:320px">
|
||
<div class="lt-range-header">
|
||
<span class="lt-range-label">Refresh Interval (s)</span>
|
||
<span class="lt-range-value">30</span>
|
||
</div>
|
||
<input type="range" class="lt-range" min="5" max="60" value="30" aria-label="Refresh Interval in seconds" aria-valuemin="5" aria-valuemax="60" aria-valuenow="30">
|
||
</div>
|
||
<div class="lt-tags">
|
||
<span class="lt-tag lt-tag--orange">CRITICAL</span>
|
||
<span class="lt-tag lt-tag--cyan">NETWORK</span>
|
||
<span class="lt-tag lt-tag--green">ONLINE</span>
|
||
<span class="lt-tag lt-tag--red">OFFLINE</span>
|
||
<span class="lt-tag lt-tag--purple">ADMIN</span>
|
||
<span class="lt-tag">UNTAGGED</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CODE BLOCK -->
|
||
<div class="lt-section-header">Code Block</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-code-block">
|
||
<div class="lt-code-header">
|
||
<span class="lt-code-lang">bash</span>
|
||
<button type="button" class="lt-code-copy" data-copy="systemctl restart nginx && systemctl status nginx">COPY</button>
|
||
</div>
|
||
<pre><code><span class="tok-cmt"># Restart and verify nginx</span>
|
||
<span class="tok-kw">systemctl</span> restart nginx <span class="tok-kw">&&</span> <span class="tok-kw">systemctl</span> status nginx
|
||
<span class="tok-cmt"># Check active connections</span>
|
||
<span class="tok-kw">ss</span> -tlnp | <span class="tok-kw">grep</span> <span class="tok-str">':80\|:443'</span></code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- KEY-VALUE + STEPPER -->
|
||
<div class="lt-section-header">Data Grid / Stepper</div>
|
||
<div class="lt-section-body" style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-lg)">
|
||
<div>
|
||
<div class="lt-kv-grid">
|
||
<div class="lt-kv-key">Hostname</div> <div class="lt-kv-val lt-kv-val--cyan">node-07.prod</div>
|
||
<div class="lt-kv-key">IP Address</div> <div class="lt-kv-val">10.0.0.7</div>
|
||
<div class="lt-kv-key">Status</div> <div class="lt-kv-val lt-kv-val--green">ONLINE</div>
|
||
<div class="lt-kv-key">Uptime</div> <div class="lt-kv-val">3d 14h 22m</div>
|
||
<div class="lt-kv-key">Load Avg</div> <div class="lt-kv-val lt-kv-val--orange">2.14 / 1.87 / 1.60</div>
|
||
<div class="lt-kv-key">Region</div> <div class="lt-kv-val">US-EAST-1</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div class="lt-stepper">
|
||
<div class="lt-step complete">
|
||
<div class="lt-step-num">✓</div>
|
||
<div class="lt-step-label">INIT</div>
|
||
</div>
|
||
<div class="lt-step complete">
|
||
<div class="lt-step-num">✓</div>
|
||
<div class="lt-step-label">BUILD</div>
|
||
</div>
|
||
<div class="lt-step active">
|
||
<div class="lt-step-num">3</div>
|
||
<div class="lt-step-label">DEPLOY</div>
|
||
</div>
|
||
<div class="lt-step">
|
||
<div class="lt-step-num">4</div>
|
||
<div class="lt-step-label">VERIFY</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LIST GROUP + BADGES -->
|
||
<div class="lt-section-header">List Group / Badges</div>
|
||
<div class="lt-section-body" style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-lg)">
|
||
<div class="lt-list-group">
|
||
<div class="lt-list-item lt-list-item--active">
|
||
<span class="lt-dot lt-dot--green"></span>
|
||
<a href="#">node-01.prod</a>
|
||
<span class="lt-list-item-meta lt-kv-val--green">ONLINE</span>
|
||
</div>
|
||
<div class="lt-list-item">
|
||
<span class="lt-dot lt-dot--green"></span>
|
||
<a href="#">node-02.prod</a>
|
||
<span class="lt-list-item-meta lt-kv-val--green">ONLINE</span>
|
||
</div>
|
||
<div class="lt-list-item">
|
||
<span class="lt-dot lt-dot--orange"></span>
|
||
<a href="#">node-03.prod</a>
|
||
<span class="lt-list-item-meta lt-kv-val--orange">DEGRADED</span>
|
||
</div>
|
||
<div class="lt-list-item">
|
||
<span class="lt-dot lt-dot--red"></span>
|
||
<a href="#">node-11.prod</a>
|
||
<span class="lt-list-item-meta lt-kv-val--red">OFFLINE</span>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;gap:var(--space-md)">
|
||
<div style="display:flex;gap:var(--space-md);align-items:center;flex-wrap:wrap">
|
||
<div class="lt-badge-wrap">
|
||
<button type="button" class="lt-btn lt-btn-sm">Alerts</button>
|
||
<span class="lt-badge">3</span>
|
||
</div>
|
||
<div class="lt-badge-wrap">
|
||
<button type="button" class="lt-btn lt-btn-sm">Messages</button>
|
||
<span class="lt-badge lt-badge--orange">12</span>
|
||
</div>
|
||
<div class="lt-badge-wrap">
|
||
<button type="button" class="lt-btn lt-btn-sm">Updates</button>
|
||
<span class="lt-badge lt-badge--cyan">5</span>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:var(--space-sm);align-items:center;flex-wrap:wrap">
|
||
<div class="lt-bar-loader"><span></span><span></span><span></span><span></span></div>
|
||
<div class="lt-spinner"></div>
|
||
<div class="lt-spinner lt-spinner--cyan lt-spinner--sm"></div>
|
||
<div class="lt-pulse-dot"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TAB BAR -->
|
||
<div class="lt-section-header">Tab Bar</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-tab-bar" role="tablist">
|
||
<button type="button" class="lt-tab active" role="tab" id="tab2-btn-overview" data-tab-target="tab-overview" aria-selected="true" aria-controls="tab-overview">Overview</button>
|
||
<button type="button" class="lt-tab" role="tab" id="tab2-btn-logs" data-tab-target="tab-logs" aria-selected="false" aria-controls="tab-logs">Logs</button>
|
||
<button type="button" class="lt-tab" role="tab" id="tab2-btn-metrics" data-tab-target="tab-metrics" aria-selected="false" aria-controls="tab-metrics">Metrics</button>
|
||
<button type="button" class="lt-tab" role="tab" id="tab2-btn-config" data-tab-target="tab-config" aria-selected="false" aria-controls="tab-config">Config</button>
|
||
</div>
|
||
<div class="lt-tab-panels" style="padding:var(--space-md) 0">
|
||
<div id="tab-overview" class="lt-tab-panel active" role="tabpanel" aria-labelledby="tab2-btn-overview">System overview: all nodes nominal. 14 active sessions.</div>
|
||
<div id="tab-logs" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab2-btn-logs">
|
||
<pre style="font-family:var(--font-mono);font-size:0.75rem;color:var(--text-secondary);margin:0">[03:41:22] nginx: 200 GET /api/nodes (12ms)
|
||
[03:41:23] cron: heartbeat OK
|
||
[03:41:24] alert: disk 87% on node-03</pre>
|
||
</div>
|
||
<div id="tab-metrics" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab2-btn-metrics">CPU avg: 34% — Mem avg: 61% — Net TX: 4.2 Mbps</div>
|
||
<div id="tab-config" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab2-btn-config">Config last updated: 2026-03-20 by admin@lotusguild.io</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TOOLTIP & DIVIDER DEMO -->
|
||
<div class="lt-section-header">Tooltips / Divider</div>
|
||
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-md)">
|
||
<div style="display:flex;gap:var(--space-lg);flex-wrap:wrap;padding-top:var(--space-md)">
|
||
<span data-tooltip="Primary accent — use for actions" class="lt-tag lt-tag--orange">ORANGE</span>
|
||
<span data-tooltip="Info / border color" data-tooltip-pos="bottom" class="lt-tag lt-tag--cyan">CYAN (bottom)</span>
|
||
<span data-tooltip="Success states" class="lt-tag lt-tag--green">GREEN</span>
|
||
<span data-tooltip="Critical / error" class="lt-tag lt-tag--red">RED</span>
|
||
</div>
|
||
<div class="lt-divider"><span class="lt-divider-label">SECTION BREAK</span></div>
|
||
<div class="lt-divider lt-divider--orange"><span class="lt-divider-label">CRITICAL ZONE</span></div>
|
||
</div>
|
||
|
||
<!-- DROPZONE -->
|
||
<div class="lt-section-header">File Dropzone</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-dropzone" data-dropzone data-accept=".json,.csv,.log" data-max-size="10485760">
|
||
<input type="file" accept=".json,.csv,.log" multiple aria-label="Upload JSON, CSV, or log files (max 10 MB each)">
|
||
<div class="lt-dropzone-icon">⤓</div>
|
||
<div class="lt-dropzone-text">Drop files here or <strong>click to browse</strong></div>
|
||
<div class="lt-dropzone-hint">Accepts .json, .csv, .log — max 10 MB each</div>
|
||
</div>
|
||
<div class="lt-file-list" id="demo-file-list"></div>
|
||
</div>
|
||
|
||
<!-- COMMAND PALETTE TRIGGER -->
|
||
<div class="lt-section-header">Command Palette</div>
|
||
<div class="lt-section-body">
|
||
<p style="font-family:var(--font-mono);font-size:0.8rem;color:var(--text-secondary);margin:0 0 var(--space-sm)">
|
||
Press <kbd style="background:var(--bg-tertiary);border:1px solid var(--border-dim);padding:2px 6px;font-family:var(--font-mono)">Ctrl+K</kbd> or click the button below to open the command palette.
|
||
</p>
|
||
<button type="button" class="lt-btn lt-btn-ghost" onclick="lt.cmdPalette.open()">⌘ Open Command Palette</button>
|
||
</div>
|
||
|
||
</div><!-- /.lt-frame-inner (v1.2 additions) -->
|
||
</div><!-- /.lt-frame (v1.2 additions) -->
|
||
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════
|
||
v1.1 COMPONENT SHOWCASE
|
||
═══════════════════════════════════════════════════════════ -->
|
||
|
||
<!-- Theme Toggle -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// THEME TOGGLE</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Dark/light mode with OS preference detection and localStorage persistence.</p>
|
||
<div class="lt-flex lt-gap-sm lt-align-center lt-wrap">
|
||
<button type="button" class="lt-btn lt-btn-primary" onclick="lt.theme.toggle()">Toggle Theme</button>
|
||
<button type="button" class="lt-btn" onclick="lt.theme.set('dark')">Force Dark</button>
|
||
<button type="button" class="lt-btn" onclick="lt.theme.set('light')">Force Light</button>
|
||
<code style="font-size:0.72rem;color:var(--accent-cyan)">lt.theme.toggle() | .set('light') | .get()</code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Skeleton Loaders -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// SKELETON LOADERS</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-3" style="gap:1rem;align-items:start">
|
||
<div class="lt-skeleton-card">
|
||
<div class="lt-skeleton-card-header">
|
||
<span class="lt-skeleton lt-skeleton-avatar"></span>
|
||
<div style="flex:1"><span class="lt-skeleton lt-skeleton-title"></span><span class="lt-skeleton lt-skeleton-line-sm"></span></div>
|
||
</div>
|
||
<span class="lt-skeleton lt-skeleton-text"></span>
|
||
<span class="lt-skeleton lt-skeleton-line-lg"></span>
|
||
<span class="lt-skeleton lt-skeleton-text" style="width:70%"></span>
|
||
<div class="lt-flex lt-gap-sm" style="margin-top:0.25rem">
|
||
<span class="lt-skeleton lt-skeleton-btn"></span>
|
||
<span class="lt-skeleton lt-skeleton-badge"></span>
|
||
</div>
|
||
</div>
|
||
<div style="grid-column:span 2">
|
||
<div class="lt-skeleton-row"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:80%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:60%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
|
||
<div class="lt-skeleton-row" style="opacity:0.65"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:65%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:50%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
|
||
<div class="lt-skeleton-row" style="opacity:0.35"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:55%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:40%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Empty States -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// EMPTY STATES</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-3" style="gap:1rem">
|
||
<div class="lt-card"><div class="lt-empty-state"><div class="lt-empty-state-icon">📭</div><div class="lt-empty-state-title">No Tickets Found</div><div class="lt-empty-state-body">No tickets match your current filters.</div><button type="button" class="lt-btn lt-btn-sm lt-btn-primary">Clear Filters</button></div></div>
|
||
<div class="lt-card"><div class="lt-empty-state"><div class="lt-empty-state-icon">🔌</div><div class="lt-empty-state-title">No Workers Online</div><div class="lt-empty-state-body">All workers are offline or unreachable.</div><button type="button" class="lt-btn lt-btn-sm" onclick="lt.toast.warning('Checking workers…')">Retry</button></div></div>
|
||
<div class="lt-card"><div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-icon">🗂</div><div class="lt-empty-state-title">No Results</div><div class="lt-empty-state-body">Try a different search term.</div></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Avatars & Notification Badges -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// AVATARS & NOTIFICATION BADGES</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-flex lt-gap-lg lt-wrap lt-align-center" style="margin-bottom:1rem">
|
||
<span class="lt-avatar lt-avatar--xs lt-avatar--orange">AB</span>
|
||
<span class="lt-avatar lt-avatar--sm lt-avatar--green">CD</span>
|
||
<span class="lt-avatar lt-avatar--purple">EF</span>
|
||
<span class="lt-avatar lt-avatar--lg lt-avatar--red">GH</span>
|
||
<div class="lt-avatar-wrap"><span class="lt-avatar lt-avatar--orange">JD</span><span class="lt-avatar-status lt-avatar-status--online"></span></div>
|
||
<div class="lt-avatar-wrap"><span class="lt-avatar">SK</span><span class="lt-avatar-status lt-avatar-status--away"></span></div>
|
||
<div class="lt-avatar-wrap"><span class="lt-avatar lt-avatar--red">MR</span><span class="lt-avatar-status lt-avatar-status--busy"></span></div>
|
||
<div class="lt-avatar-group">
|
||
<span class="lt-avatar lt-avatar--sm lt-avatar--orange">AA</span>
|
||
<span class="lt-avatar lt-avatar--sm lt-avatar--green">BB</span>
|
||
<span class="lt-avatar lt-avatar--sm lt-avatar--purple">CC</span>
|
||
<span class="lt-avatar lt-avatar--sm" style="background:var(--bg-tertiary);color:var(--text-muted);font-size:0.6rem">+4</span>
|
||
</div>
|
||
</div>
|
||
<div class="lt-flex lt-gap-md lt-align-center lt-wrap">
|
||
<span class="lt-notif-wrap" id="demo-notif-btn"><button type="button" class="lt-btn lt-btn-sm">🔔 Alerts</button></span>
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.notif.inc('#demo-notif-btn')">+1 Badge</button>
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.notif.clear('#demo-notif-btn')">Clear</button>
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.notif.inc('#lt-notif-bell')">+1 Header Bell</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Timeline -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// TIMELINE / ACTIVITY FEED</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
|
||
<div class="lt-timeline">
|
||
<div class="lt-timeline-item lt-timeline-item--red">
|
||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">alertmanager</span> fired<span class="lt-timeline-time lt-num">14:22:05</span></div>
|
||
<div class="lt-timeline-body">CRITICAL: Storage array link-down on <code>compute-storage-01</code>. Ticket auto-created.</div>
|
||
</div>
|
||
<div class="lt-timeline-item lt-timeline-item--orange">
|
||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> assigned<span class="lt-timeline-time lt-num">14:24:11</span></div>
|
||
<div class="lt-timeline-body">Escalated to P1 Critical. Paged on-call team.</div>
|
||
</div>
|
||
<div class="lt-timeline-item">
|
||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> commented<span class="lt-timeline-time lt-num">14:31:44</span></div>
|
||
<div class="lt-timeline-body">Confirmed NIC failure. Ordered replacement hardware. ETA 2h.</div>
|
||
</div>
|
||
<div class="lt-timeline-item lt-timeline-item--green">
|
||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> resolved<span class="lt-timeline-time lt-num">16:55:00</span></div>
|
||
<div class="lt-timeline-body">Hardware replaced. Link restored. Monitoring for 30 min.</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;gap:0.75rem">
|
||
<div class="lt-stat-card"><div class="lt-stat-label">Resolution Time</div><div class="lt-stat-value lt-text-green lt-num">2h 33m</div></div>
|
||
<div class="lt-stat-card"><div class="lt-stat-label">Events</div><div class="lt-stat-value lt-num">4</div></div>
|
||
<div class="lt-stat-card"><div class="lt-stat-label">SLA Status</div><div class="lt-stat-value lt-text-orange">Within SLA</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right Drawer -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// RIGHT-SIDE DRAWER</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Detail/inspect panel from the right. Focus trap, ESC close, overlay backdrop, return-focus.</p>
|
||
<div class="lt-flex lt-gap-sm lt-wrap">
|
||
<button type="button" class="lt-btn lt-btn-primary" data-drawer-open="lt-detail-drawer">Open Detail Panel</button>
|
||
<code style="font-size:0.72rem;color:var(--accent-cyan)">lt.rightDrawer.open('id') | .close() | .toggle()</code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Context Menu -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// CONTEXT MENU</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Right-click any element with <code>data-context-menu</code> or trigger programmatically.</p>
|
||
<div class="lt-flex lt-gap-sm lt-wrap lt-align-center">
|
||
<div data-context-menu="demo-ctx" class="lt-card" style="padding:0.75rem 1.25rem;cursor:context-menu;border-style:dashed;display:inline-block">
|
||
Right-click this card ›
|
||
</div>
|
||
<button type="button" class="lt-btn lt-btn-sm" id="demo-ctx-btn">Show Context Menu</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Combobox + Typeahead -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// COMBOBOX & TYPEAHEAD</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
|
||
<div>
|
||
<label class="lt-label" for="demo-combobox-input">Assign Workers (multi-select)</label>
|
||
<div class="lt-combobox" id="demo-combobox">
|
||
<div class="lt-combobox-input-wrap">
|
||
<input type="text" class="lt-combobox-input" id="demo-combobox-input" placeholder="Search workers…" autocomplete="off">
|
||
</div>
|
||
<div class="lt-combobox-dropdown"></div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="lt-label" for="demo-typeahead-input">Search Tickets (typeahead)</label>
|
||
<div class="lt-typeahead" id="demo-typeahead">
|
||
<input type="text" class="lt-input lt-w-full" id="demo-typeahead-input" placeholder="Type to search…" autocomplete="off">
|
||
<div class="lt-typeahead-dropdown"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sticky Table -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// STICKY TABLE HEADERS</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-table-sticky-wrap">
|
||
<table class="lt-table lt-table-responsive">
|
||
<thead><tr><th scope="col">ID</th><th scope="col">Priority</th><th scope="col">Title</th><th scope="col">Status</th><th scope="col">Assignee</th></tr></thead>
|
||
<tbody>
|
||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#001</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p1">P1</span></td><td data-label="Title">Link-down on compute-storage-01</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">jdoe</td></tr>
|
||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#002</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p2">P2</span></td><td data-label="Title">Switch port flapping USW-Pro-24</td><td data-label="Status"><span class="lt-badge lt-badge-in-progress">In Progress</span></td><td data-label="Assignee">smith</td></tr>
|
||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#003</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p3">P3</span></td><td data-label="Title">Scheduled SFP+ replacement</td><td data-label="Status"><span class="lt-badge lt-badge-pending">Pending</span></td><td data-label="Assignee">ops-bot</td></tr>
|
||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#004</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p4">P4</span></td><td data-label="Title">SSL cert renewal wiki</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">admin</td></tr>
|
||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#005</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p1">P1</span></td><td data-label="Title">RAID controller firmware</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">jdoe</td></tr>
|
||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#006</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p2">P2</span></td><td data-label="Title">Backup job failure nas-01</td><td data-label="Status"><span class="lt-badge lt-badge-closed">Closed</span></td><td data-label="Assignee">backup-bot</td></tr>
|
||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#007</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p3">P3</span></td><td data-label="Title">Prometheus alert rule tuning</td><td data-label="Status"><span class="lt-badge lt-badge-in-progress">In Progress</span></td><td data-label="Assignee">ops-team</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chart Containers -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// CHART CONTAINERS</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-2" style="gap:1rem;grid-template-columns:repeat(auto-fit,minmax(240px,1fr))">
|
||
<div class="lt-chart-wrap">
|
||
<div class="lt-chart-header">
|
||
<span class="lt-chart-title">Ticket Volume (7d)</span>
|
||
<div class="lt-chart-legend">
|
||
<span class="lt-chart-legend-item"><span class="lt-chart-legend-dot" style="background:var(--accent-cyan)"></span>Open</span>
|
||
<span class="lt-chart-legend-item"><span class="lt-chart-legend-dot" style="background:var(--accent-green)"></span>Closed</span>
|
||
</div>
|
||
</div>
|
||
<div class="lt-chart-body" style="min-height:120px;display:flex;align-items:center;justify-content:center;color:var(--text-muted);font-size:0.72rem;letter-spacing:0.1em">[ Plug in Chart.js / D3 here ]</div>
|
||
<div class="lt-chart-axis"><span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span></div>
|
||
</div>
|
||
<div class="lt-chart-wrap is-loading">
|
||
<div class="lt-chart-header"><span class="lt-chart-title">Worker Uptime</span></div>
|
||
<div class="lt-chart-body"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Split Pane -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// SPLIT PANE</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-split" id="demo-split" style="height:clamp(160px,30vh,240px);border:1px solid var(--border-dim)">
|
||
<div class="lt-split-pane" style="padding:1rem;overflow:auto">
|
||
<div class="lt-section-label" style="margin-bottom:0.5rem">Panel A</div>
|
||
<p style="font-size:0.78rem;color:var(--text-secondary)">Drag the divider to resize. Stacks vertically on mobile.</p>
|
||
</div>
|
||
<div class="lt-split-divider" title="Drag to resize"></div>
|
||
<div class="lt-split-pane" style="padding:1rem;overflow:auto">
|
||
<div class="lt-section-label" style="margin-bottom:0.5rem">Panel B</div>
|
||
<p style="font-size:0.78rem;color:var(--text-secondary)">Both panels maintain independent scrolling.</p>
|
||
</div>
|
||
</div>
|
||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.5rem"><code>lt.splitPane.init(el, { initial: 0.4, minA: 120 })</code></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- WebSocket & Offline -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// WEBSOCKET & OFFLINE DETECTION</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-flex lt-gap-md lt-wrap lt-align-center" style="margin-bottom:1rem">
|
||
<div class="lt-ws-status" data-state="connected"><span class="lt-dot"></span><span>Connected</span></div>
|
||
<div class="lt-ws-status" data-state="connecting"><span class="lt-dot"></span><span>Connecting…</span></div>
|
||
<div class="lt-ws-status" data-state="disconnected"><span class="lt-dot"></span><span>Disconnected</span></div>
|
||
</div>
|
||
<div class="lt-flex lt-gap-sm lt-wrap">
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.offline.isOnline() ? lt.toast.success('Online ✓') : lt.toast.error('Offline ✗')">Check Online Status</button>
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.toast.info('lt.ws.connect(url, { reconnect:true, onMessage: fn })')">WS API Hint</button>
|
||
</div>
|
||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.75rem">Offline banner + body class auto-applied on <code>navigator.onLine</code> change. WS manager has exponential backoff, event emitter, status indicator binding.</p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- Wizard / Multi-Step Form -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// WIZARD / MULTI-STEP FORM</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div id="demo-wizard">
|
||
<!-- Step indicators -->
|
||
<div class="lt-wizard-steps">
|
||
<div class="lt-wizard-step is-active" data-wizard-indicator aria-current="step">
|
||
<div class="lt-wizard-num">1</div>
|
||
<div class="lt-wizard-label">Details</div>
|
||
</div>
|
||
<div class="lt-wizard-step" data-wizard-indicator>
|
||
<div class="lt-wizard-num">2</div>
|
||
<div class="lt-wizard-label">Assign</div>
|
||
</div>
|
||
<div class="lt-wizard-step" data-wizard-indicator>
|
||
<div class="lt-wizard-num">3</div>
|
||
<div class="lt-wizard-label">Review</div>
|
||
</div>
|
||
</div>
|
||
<!-- Counter -->
|
||
<p class="lt-wizard-counter">Step <strong data-wizard-current>1</strong> of <strong data-wizard-total>3</strong></p>
|
||
<!-- Steps -->
|
||
<div data-wizard-step="1" class="is-active">
|
||
<div class="lt-grid lt-grid-2" style="gap:1rem;margin-top:1rem">
|
||
<div class="lt-form-group"><label class="lt-label" for="wizard-title">Ticket Title</label><input id="wizard-title" type="text" name="title" class="lt-input lt-w-full" placeholder="Brief description…"></div>
|
||
<div class="lt-form-group"><label class="lt-label" for="wizard-priority">Priority</label><select id="wizard-priority" name="priority" class="lt-select lt-w-full"><option value="p1">P1 Critical</option><option value="p2">P2 High</option><option value="p3" selected>P3 Medium</option><option value="p4">P4 Low</option></select></div>
|
||
</div>
|
||
</div>
|
||
<div data-wizard-step="2" aria-hidden="true">
|
||
<div class="lt-form-group" style="margin-top:1rem"><label class="lt-label" for="wizard-assignee">Assign To</label><input id="wizard-assignee" type="text" name="assignee" class="lt-input lt-w-full" placeholder="Username…"></div>
|
||
<div class="lt-form-group"><label class="lt-label" for="wizard-due">Due Date</label><input id="wizard-due" type="date" name="due" class="lt-input"></div>
|
||
</div>
|
||
<div data-wizard-step="3" aria-hidden="true">
|
||
<div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-icon">✅</div><div class="lt-empty-state-title">Review & Submit</div><div class="lt-empty-state-body">Check the details above and click Submit when ready.</div></div>
|
||
</div>
|
||
<!-- Nav -->
|
||
<div class="lt-wizard-nav">
|
||
<button type="button" class="lt-btn lt-btn-sm" data-wizard-prev disabled>← Back</button>
|
||
<button type="button" class="lt-btn lt-btn-primary lt-btn-sm" data-wizard-next>Next →</button>
|
||
<button type="button" class="lt-btn lt-btn-primary lt-btn-sm" data-wizard-done style="display:none">Submit ✓</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sortable List -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// DRAG-TO-REORDER (SORTABLE)</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-2" style="gap:1.5rem;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))">
|
||
<div>
|
||
<p style="font-size:0.75rem;color:var(--text-muted);margin-bottom:0.75rem">Drag items to reorder. Uses native HTML5 drag API with pointer-events fallback.</p>
|
||
<ul id="demo-sortable" style="list-style:none;padding:0;display:flex;flex-direction:column;gap:4px">
|
||
<li data-id="p1" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P1 — Storage link-down</li>
|
||
<li data-id="p2" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P2 — Switch port flapping</li>
|
||
<li data-id="p3" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P3 — SFP+ replacement</li>
|
||
<li data-id="p4" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P4 — SSL cert renewal</li>
|
||
</ul>
|
||
<p id="demo-sort-order" style="font-size:0.68rem;color:var(--text-muted);margin-top:0.5rem">Order: p1, p2, p3, p4</p>
|
||
</div>
|
||
<div>
|
||
<p style="font-size:0.72rem;color:var(--text-muted);margin-bottom:0.75rem">API:</p>
|
||
<pre class="lt-code-block" style="font-size:0.7rem">const s = lt.sortable.init(listEl, {
|
||
onSort: (items) => console.log(s.getOrder())
|
||
});</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Countdown / Timer -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// COUNTDOWN & TIMER</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-3" style="gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))">
|
||
<div class="lt-stat-card">
|
||
<div class="lt-stat-label">SLA Countdown</div>
|
||
<div class="lt-countdown lt-num" id="demo-countdown">--:--:--</div>
|
||
<div style="font-size:0.68rem;color:var(--text-muted);margin-top:0.25rem">Goes urgent (red) at <5 min</div>
|
||
</div>
|
||
<div class="lt-stat-card">
|
||
<div class="lt-stat-label">Stopwatch</div>
|
||
<div class="lt-countdown lt-num" id="demo-stopwatch">00:00:00</div>
|
||
<div class="lt-flex lt-gap-xs" style="margin-top:0.5rem">
|
||
<button type="button" class="lt-btn lt-btn-sm" id="sw-pause">Pause</button>
|
||
<button type="button" class="lt-btn lt-btn-sm" id="sw-reset">Reset</button>
|
||
</div>
|
||
</div>
|
||
<div class="lt-stat-card">
|
||
<div class="lt-stat-label">API</div>
|
||
<pre class="lt-code-block" style="font-size:0.62rem;margin:0">lt.timer.countdown(el, date, {
|
||
urgent: 300,
|
||
onExpire: () => {}
|
||
});
|
||
lt.timer.stopwatch(el);</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Image Lightbox -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// IMAGE LIGHTBOX</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Click any image to open full-screen viewer. Keyboard: ←/→ navigate, Esc closes.</p>
|
||
<div class="lt-flex lt-gap-md lt-wrap">
|
||
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt1/320/180" alt="Server rack overview" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
|
||
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt2/320/180" alt="Network switch closeup" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
|
||
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt3/320/180" alt="Datacenter floor view" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
|
||
</div>
|
||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.5rem"><code>lt.lightbox.init('.lt-lightbox-demo', { caption: 'alt', loop: true })</code></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sidebar Submenus -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// SIDEBAR SUBMENUS</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div style="width:220px;background:var(--bg-secondary);border:1px solid var(--border-dim);padding:0.5rem">
|
||
<a href="#" class="lt-sidebar-link active" style="display:flex;align-items:center;gap:0.5rem;padding:0.4rem 0.75rem;font-size:0.78rem;color:var(--accent-orange);text-decoration:none">⊞ Dashboard</a>
|
||
<a href="#" class="lt-sidebar-link" style="display:flex;align-items:center;gap:0.5rem;padding:0.4rem 0.75rem;font-size:0.78rem;color:var(--text-secondary);text-decoration:none">🎫 Tickets</a>
|
||
<div class="lt-sidebar-group is-open">
|
||
<div class="lt-sidebar-group-label">Admin <span class="chevron">▶</span></div>
|
||
<div class="lt-sidebar-submenu">
|
||
<a href="#" class="lt-sidebar-sub-link active" aria-current="page">⚙ Templates</a>
|
||
<a href="#" class="lt-sidebar-sub-link">🔀 Workflow</a>
|
||
<a href="#" class="lt-sidebar-sub-link">📋 Audit Log</a>
|
||
<a href="#" class="lt-sidebar-sub-link">🔑 API Keys</a>
|
||
</div>
|
||
</div>
|
||
<div class="lt-sidebar-group">
|
||
<div class="lt-sidebar-group-label">Reports <span class="chevron">▶</span></div>
|
||
<div class="lt-sidebar-submenu">
|
||
<a href="#" class="lt-sidebar-sub-link">📊 SLA Report</a>
|
||
<a href="#" class="lt-sidebar-sub-link">📈 Volume Trends</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Markdown Renderer -->
|
||
<div class="lt-section">
|
||
<div class="lt-section-header">
|
||
<span class="lt-section-title">// MARKDOWN RENDERER</span>
|
||
</div>
|
||
<div class="lt-section-body">
|
||
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
|
||
<div>
|
||
<div class="lt-section-label" style="margin-bottom:0.5rem">Source</div>
|
||
<pre class="lt-code-block" style="font-size:0.7rem"># Incident Report
|
||
**Severity**: P1 Critical
|
||
|
||
## Summary
|
||
Storage array link-down on `compute-storage-01`.
|
||
|
||
- Affected: 3 production services
|
||
- Duration: 2h 33m
|
||
- Root cause: NIC hardware failure
|
||
|
||
> Resolved. Monitoring continues.
|
||
|
||
[View Ticket](#001)</pre>
|
||
</div>
|
||
<div>
|
||
<div class="lt-section-label" style="margin-bottom:0.5rem">Rendered</div>
|
||
<div id="demo-markdown" class="lt-markdown" style="background:var(--bg-card);border:1px solid var(--border-dim);padding:1rem" data-markdown="# Incident Report **Severity**: P1 Critical ## Summary Storage array link-down on `compute-storage-01`. - Affected: 3 production services - Duration: 2h 33m - Root cause: NIC hardware failure > Resolved. Monitoring continues. [View Ticket](#001)"></div>
|
||
</div>
|
||
</div>
|
||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.75rem">Built-in micro-renderer (no deps). Drops in <code>window.marked</code> or <code>window.markdownit</code> automatically if present.</p>
|
||
</div>
|
||
</div>
|
||
|
||
</main><!-- /.lt-main -->
|
||
|
||
<!-- ================================================================
|
||
FOOTER LANDMARK
|
||
================================================================ -->
|
||
<footer class="lt-footer" role="contentinfo" aria-label="Application footer">
|
||
<span class="lt-text-muted lt-font-mono lt-text-xs">
|
||
LotusGuild Terminal Design System v1.2 — Internal Use Only
|
||
</span>
|
||
<span class="lt-text-muted lt-font-mono lt-text-xs">
|
||
© <span id="footer-year"></span> LotusGuild. All rights reserved.
|
||
</span>
|
||
</footer>
|
||
|
||
|
||
<!-- ===========================================================
|
||
TOAST DEMO BUTTONS (remove in production)
|
||
=========================================================== -->
|
||
<div style="position:fixed;bottom:1rem;left:1rem;display:flex;flex-direction:column;gap:0.5rem;z-index:900">
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.toast.success('Ticket saved successfully')">✓ Toast</button>
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger" onclick="lt.toast.error('Network error — retry in 5s', 5000)">✗ Error</button>
|
||
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.toast.warning('Rate limit 80% used')">! Warn</button>
|
||
<button type="button" class="lt-btn lt-btn-sm lt-btn-ghost" onclick="lt.toast.info('Auto-refresh triggered')">i Info</button>
|
||
</div>
|
||
|
||
<!-- ===========================================================
|
||
MODAL EXAMPLE: Export
|
||
=========================================================== -->
|
||
<div id="export-modal" class="lt-modal-overlay" aria-hidden="true">
|
||
<div class="lt-modal" role="dialog" aria-modal="true" aria-labelledby="export-modal-title">
|
||
<div class="lt-modal-header">
|
||
<span class="lt-modal-title" id="export-modal-title">Export Tickets</span>
|
||
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||
</div>
|
||
<div class="lt-modal-body">
|
||
<div class="lt-form-group">
|
||
<label class="lt-label" for="export-fmt">Format</label>
|
||
<select id="export-fmt" class="lt-select">
|
||
<option>CSV</option>
|
||
<option>JSON</option>
|
||
</select>
|
||
</div>
|
||
<div class="lt-form-group">
|
||
<label class="lt-label">
|
||
<input type="checkbox" class="lt-checkbox" checked> Selected tickets only
|
||
</label>
|
||
</div>
|
||
<div class="lt-msg lt-msg-info">Exports include all visible columns.</div>
|
||
</div>
|
||
<div class="lt-modal-footer">
|
||
<button type="button" class="lt-btn lt-btn-ghost" data-modal-close>Cancel</button>
|
||
<button type="button" class="lt-btn lt-btn-primary" onclick="lt.toast.success('Export started'); lt.modal.close('export-modal')">Export</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===========================================================
|
||
MODAL EXAMPLE: Keyboard shortcuts help
|
||
=========================================================== -->
|
||
<div id="lt-keys-help" class="lt-modal-overlay" aria-hidden="true">
|
||
<div class="lt-modal" role="dialog" aria-modal="true" aria-labelledby="keys-help-title">
|
||
<div class="lt-modal-header">
|
||
<span class="lt-modal-title" id="keys-help-title">Keyboard Shortcuts</span>
|
||
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||
</div>
|
||
<div class="lt-modal-body">
|
||
<table class="lt-data-table" style="width:100%">
|
||
<thead>
|
||
<tr><th scope="col">Shortcut</th><th scope="col">Action</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>Ctrl / ⌘ + K</td><td>Focus search box</td></tr>
|
||
<tr><td>Ctrl / ⌘ + E</td><td>Toggle edit mode (ticket page)</td></tr>
|
||
<tr><td>Ctrl / ⌘ + S</td><td>Save changes (ticket page)</td></tr>
|
||
<tr><td>j / ↓</td><td>Select next row</td></tr>
|
||
<tr><td>k / ↑</td><td>Select previous row</td></tr>
|
||
<tr><td>Enter</td><td>Open selected ticket</td></tr>
|
||
<tr><td>n</td><td>New ticket</td></tr>
|
||
<tr><td>?</td><td>Show this help</td></tr>
|
||
<tr><td>ESC</td><td>Close modal / cancel</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="lt-modal-footer">
|
||
<button type="button" class="lt-btn" data-modal-close>Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===========================================================
|
||
COMMAND PALETTE OVERLAY
|
||
=========================================================== -->
|
||
<div id="lt-cmd-overlay" class="lt-cmd-overlay" role="dialog" aria-modal="true" aria-label="Command palette">
|
||
<div class="lt-cmd-palette" id="lt-cmd-palette">
|
||
<div class="lt-cmd-input-wrap">
|
||
<span class="lt-cmd-prompt">></span>
|
||
<input id="lt-cmd-input" class="lt-cmd-input" type="text" placeholder="Search commands…" autocomplete="off" spellcheck="false" aria-label="Search commands">
|
||
</div>
|
||
<div class="lt-cmd-results" id="lt-cmd-results">
|
||
<div class="lt-cmd-empty">Start typing to search…</div>
|
||
</div>
|
||
<div class="lt-cmd-footer">
|
||
<span><kbd>↑</kbd><kbd>↓</kbd> Navigate</span>
|
||
<span><kbd>Enter</kbd> Select</span>
|
||
<span><kbd>Esc</kbd> Close</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===========================================================
|
||
SCRIPTS
|
||
=========================================================== -->
|
||
|
||
<!--
|
||
PHP apps inject CSRF token + config here (with CSP nonce):
|
||
<script nonce="<?php echo $nonce; ?>">
|
||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||
window.APP_TIMEZONE = '<?php echo $config['TIMEZONE']; ?>';
|
||
</script>
|
||
|
||
Node/Express (EJS):
|
||
<script nonce="<%= nonce %>">
|
||
window.CSRF_TOKEN = '<%= csrfToken %>';
|
||
</script>
|
||
|
||
Flask/Jinja2:
|
||
<script nonce="{{ nonce }}">
|
||
window.CSRF_TOKEN = '{{ csrf_token() }}';
|
||
</script>
|
||
-->
|
||
|
||
<!-- LotusGuild Terminal Design System JS v1.2 -->
|
||
<script src="/web_template/base.js"></script>
|
||
|
||
<!-- Init v1.2 modules -->
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// One call initializes accordion, tooltip, alerts, clipboard, sidebar, submenus + boot.
|
||
// Pass { boot: false } to skip the terminal boot animation.
|
||
lt.init({ bootName: 'MY APP' });
|
||
|
||
// Command palette demo commands
|
||
lt.cmdPalette.init([
|
||
{
|
||
group: 'Navigation',
|
||
items: [
|
||
{ icon: '⌂', label: 'Dashboard', kbd: 'G D', action: () => lt.toast.info('→ Dashboard') },
|
||
{ icon: '⊞', label: 'All Tickets', kbd: 'G T', action: () => lt.toast.info('→ Tickets') },
|
||
{ icon: '★', label: 'Saved Filters', action: () => lt.toast.info('→ Filters') },
|
||
]
|
||
},
|
||
{
|
||
group: 'Actions',
|
||
items: [
|
||
{ icon: '+', label: 'New Ticket', kbd: 'N', action: () => lt.toast.success('New ticket form') },
|
||
{ icon: '⤓', label: 'Export CSV', action: () => lt.modal.open('export-modal') },
|
||
{ icon: '⟳', label: 'Refresh Data', kbd: 'R', action: () => lt.toast.info('Refreshing...') },
|
||
]
|
||
},
|
||
{
|
||
group: 'Help',
|
||
items: [
|
||
{ icon: '?', label: 'Keyboard Shortcuts', kbd: '?', action: () => lt.modal.open('lt-keys-help') },
|
||
]
|
||
},
|
||
]);
|
||
|
||
// Range slider live value display
|
||
document.querySelectorAll('input[type="range"].lt-range').forEach(r => {
|
||
const wrap = r.closest('.lt-range-wrap');
|
||
const label = wrap && wrap.querySelector('.lt-range-value');
|
||
if (label) {
|
||
label.textContent = r.value;
|
||
r.addEventListener('input', () => { label.textContent = r.value; r.setAttribute('aria-valuenow', r.value); });
|
||
}
|
||
});
|
||
|
||
// Animate the demo progress bars on load
|
||
document.querySelectorAll('.lt-progress-bar[data-width]').forEach(bar => {
|
||
requestAnimationFrame(() => { bar.style.width = bar.dataset.width; });
|
||
});
|
||
|
||
// Theme toggle button
|
||
document.getElementById('lt-theme-btn').addEventListener('click', () => lt.theme.toggle());
|
||
|
||
// Context menu: register demo menu items
|
||
lt.contextMenu.register('demo-ctx', [
|
||
{ icon: '📋', label: 'Copy ID', kbd: 'C', action: () => lt.toast.info('ID copied') },
|
||
{ icon: '👁', label: 'View Details', action: () => lt.rightDrawer.open('lt-detail-drawer') },
|
||
{ icon: '✏️', label: 'Edit Ticket', kbd: 'E', action: () => lt.toast.info('Edit…') },
|
||
{ divider: true },
|
||
{ icon: '🗑', label: 'Delete', danger: true, action: () => lt.toast.error('Deleted') },
|
||
]);
|
||
// Programmatic context menu button
|
||
const demoCtxBtn = document.getElementById('demo-ctx-btn');
|
||
if (demoCtxBtn) demoCtxBtn.addEventListener('click', e => {
|
||
const r = demoCtxBtn.getBoundingClientRect();
|
||
lt.contextMenu.show(r.left, r.bottom + 4, [
|
||
{ icon: '📋', label: 'Copy ID', action: () => lt.toast.info('Copied') },
|
||
{ icon: '✏️', label: 'Edit', action: () => lt.toast.info('Edit') },
|
||
{ divider: true },
|
||
{ icon: '🗑', label: 'Delete', danger: true, action: () => lt.toast.error('Deleted') },
|
||
]);
|
||
});
|
||
|
||
// Combobox demo
|
||
lt.combobox.init(
|
||
document.getElementById('demo-combobox-input'),
|
||
[
|
||
{ value: 'worker-01', label: 'worker-01', icon: '🖥' },
|
||
{ value: 'worker-02', label: 'worker-02', icon: '🖥' },
|
||
{ value: 'worker-03', label: 'worker-03', icon: '🖥' },
|
||
{ value: 'gpu-01', label: 'gpu-01', icon: '⚡' },
|
||
{ value: 'gpu-02', label: 'gpu-02', icon: '⚡' },
|
||
{ value: 'storage-01',label: 'storage-01',icon: '💾' },
|
||
],
|
||
{ onChange: vals => console.log('[combobox]', vals) }
|
||
);
|
||
|
||
// Typeahead demo
|
||
lt.typeahead.init(
|
||
document.getElementById('demo-typeahead-input'),
|
||
[
|
||
{ value: '001', label: 'Link-down on compute-storage-01', icon: '🔴', meta: 'P1' },
|
||
{ value: '002', label: 'Switch port flapping USW-Pro-24', icon: '🟠', meta: 'P2' },
|
||
{ value: '003', label: 'Scheduled SFP+ replacement large1',icon: '🔵', meta: 'P3' },
|
||
{ value: '004', label: 'SSL cert renewal wiki.lotusguild.org', icon: '🟢', meta: 'P4' },
|
||
{ value: '005', label: 'RAID controller firmware update', icon: '🔴', meta: 'P1' },
|
||
],
|
||
{ onSelect: item => lt.toast.info(`Selected: ${item.label}`) }
|
||
);
|
||
|
||
// Split pane demo
|
||
lt.splitPane.init(document.getElementById('demo-split'), { initial: 0.4, minA: 100, minB: 100 });
|
||
|
||
// Demo notification badge initial count
|
||
lt.notif.set('#lt-notif-bell', 3);
|
||
|
||
// Wizard demo
|
||
lt.wizard.init(document.getElementById('demo-wizard'), {
|
||
onComplete: data => lt.toast.success('Ticket submitted: ' + (data.title || 'untitled')),
|
||
validate: (step, data) => {
|
||
if (step === 1 && !data.title?.trim()) { lt.toast.error('Title is required'); return false; }
|
||
return true;
|
||
},
|
||
});
|
||
|
||
// Sortable demo
|
||
const sortableList = lt.sortable.init(document.getElementById('demo-sortable'), {
|
||
onSort: (items) => {
|
||
document.getElementById('demo-sort-order').textContent = 'Order: ' + items.map(el => el.dataset.id).join(', ');
|
||
},
|
||
});
|
||
|
||
// Kanban drag-and-drop — all four columns share group "kanban"
|
||
['kanban-col-open','kanban-col-pending','kanban-col-inprogress','kanban-col-closed'].forEach(id => {
|
||
const col = document.getElementById(id);
|
||
if (col) lt.sortable.init(col, { group: 'kanban', onSort: () => {} });
|
||
});
|
||
|
||
// Pagination demo (50 items, 10 per page)
|
||
lt.pagination.init('#demo-pagination', {
|
||
total: 50, perPage: 10, page: 2,
|
||
onChange: p => lt.toast.info('Page ' + p),
|
||
});
|
||
|
||
// Countdown demo — SLA expires 2 hours from now
|
||
const slaTarget = new Date(Date.now() + 2 * 60 * 60 * 1000);
|
||
lt.timer.countdown(document.getElementById('demo-countdown'), slaTarget, {
|
||
urgent: 300,
|
||
urgentClass: 'lt-text-red lt-countdown-urgent',
|
||
onExpire: () => lt.toast.error('SLA BREACHED'),
|
||
});
|
||
|
||
// Stopwatch demo
|
||
const sw = lt.timer.stopwatch(document.getElementById('demo-stopwatch'));
|
||
let swRunning = true;
|
||
document.getElementById('sw-pause').addEventListener('click', function() {
|
||
if (swRunning) { sw.pause(); this.textContent = 'Resume'; } else { sw.resume(); this.textContent = 'Pause'; }
|
||
swRunning = !swRunning;
|
||
});
|
||
document.getElementById('sw-reset').addEventListener('click', () => { sw.reset(); swRunning = true; document.getElementById('sw-pause').textContent = 'Pause'; });
|
||
|
||
// Lightbox demo
|
||
lt.lightbox.init('.lt-lightbox-demo');
|
||
|
||
// Markdown demo
|
||
lt.markdown.init('#demo-markdown');
|
||
|
||
// Sidebar submenus (re-init for demo sidebar)
|
||
lt.sidebarSubmenus.init(document.querySelector('.lt-section-body'));
|
||
|
||
// ----- Notification bell dropdown -----
|
||
(function() {
|
||
const btn = document.getElementById('lt-notif-bell-btn');
|
||
const panel = document.getElementById('lt-notif-panel');
|
||
if (!btn || !panel) return;
|
||
|
||
function open() {
|
||
panel.removeAttribute('aria-hidden');
|
||
btn.setAttribute('aria-expanded', 'true');
|
||
// Mark all as read visually
|
||
}
|
||
function close(restoreFocus) {
|
||
panel.setAttribute('aria-hidden', 'true');
|
||
btn.setAttribute('aria-expanded', 'false');
|
||
if (restoreFocus) btn.focus();
|
||
}
|
||
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
panel.hasAttribute('aria-hidden') ? open() : close();
|
||
});
|
||
|
||
// "Mark all read" button
|
||
const clearBtn = document.getElementById('lt-notif-clear-all');
|
||
if (clearBtn) clearBtn.addEventListener('click', () => {
|
||
panel.querySelectorAll('.lt-notif-item--unread').forEach(el => el.classList.remove('lt-notif-item--unread'));
|
||
panel.querySelectorAll('.lt-notif-dot').forEach(el => { el.classList.remove('lt-notif-dot'); el.classList.add('lt-notif-dot', 'lt-notif-dot--read'); });
|
||
lt.notif.clear('#lt-notif-bell');
|
||
lt.toast.info('All notifications marked as read');
|
||
});
|
||
|
||
// Individual item click + keyboard activation
|
||
panel.querySelectorAll('.lt-notif-item').forEach(item => {
|
||
const activate = () => {
|
||
item.classList.remove('lt-notif-item--unread');
|
||
const dot = item.querySelector('.lt-notif-dot');
|
||
if (dot) { dot.classList.add('lt-notif-dot--read'); }
|
||
close();
|
||
};
|
||
item.addEventListener('click', activate);
|
||
item.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); activate(); } });
|
||
});
|
||
|
||
// Close on outside click
|
||
document.addEventListener('click', e => {
|
||
if (!document.getElementById('lt-notif-bell').contains(e.target)) close();
|
||
});
|
||
|
||
// Esc close — restore focus to bell button
|
||
document.addEventListener('keydown', e => { if (e.key === 'Escape' && !panel.hasAttribute('aria-hidden')) close(true); });
|
||
}());
|
||
|
||
// ----- Generic dropdown toggle (Advanced filter + Bulk Actions) -----
|
||
document.querySelectorAll('.lt-dropdown-trigger').forEach(btn => {
|
||
const wrap = btn.closest('.lt-dropdown-wrap');
|
||
const panel = wrap && wrap.querySelector('.lt-dropdown-panel');
|
||
if (!panel) return;
|
||
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const isOpen = !panel.hasAttribute('aria-hidden');
|
||
// Close all other dropdowns first
|
||
document.querySelectorAll('.lt-dropdown-panel:not([aria-hidden])').forEach(p => {
|
||
p.setAttribute('aria-hidden', 'true');
|
||
const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
|
||
if (t) t.setAttribute('aria-expanded', 'false');
|
||
});
|
||
if (!isOpen) {
|
||
panel.removeAttribute('aria-hidden');
|
||
btn.setAttribute('aria-expanded', 'true');
|
||
}
|
||
});
|
||
});
|
||
|
||
// Close dropdowns on outside click / Esc
|
||
document.addEventListener('click', () => {
|
||
document.querySelectorAll('.lt-dropdown-panel:not([aria-hidden])').forEach(p => {
|
||
p.setAttribute('aria-hidden', 'true');
|
||
const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
|
||
if (t) t.setAttribute('aria-expanded', 'false');
|
||
});
|
||
});
|
||
document.addEventListener('keydown', e => {
|
||
if (e.key !== 'Escape') return;
|
||
document.querySelectorAll('.lt-dropdown-panel:not([aria-hidden])').forEach(p => {
|
||
p.setAttribute('aria-hidden', 'true');
|
||
const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
|
||
if (t) { t.setAttribute('aria-expanded', 'false'); t.focus(); }
|
||
});
|
||
});
|
||
|
||
// Footer year
|
||
const footerYear = document.getElementById('footer-year');
|
||
if (footerYear) footerYear.textContent = new Date().getFullYear();
|
||
|
||
// Tab bar switching
|
||
document.querySelectorAll('.lt-tab-bar').forEach(bar => {
|
||
bar.addEventListener('click', e => {
|
||
const tab = e.target.closest('.lt-tab');
|
||
if (!tab) return;
|
||
const targetId = tab.dataset.tabTarget;
|
||
if (!targetId) return;
|
||
const panels = bar.nextElementSibling;
|
||
bar.querySelectorAll('.lt-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
|
||
tab.classList.add('active');
|
||
tab.setAttribute('aria-selected', 'true');
|
||
if (panels) {
|
||
panels.querySelectorAll('.lt-tab-panel').forEach(p => p.classList.remove('active'));
|
||
const target = panels.querySelector('#' + targetId);
|
||
if (target) target.classList.add('active');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|