* Make /home install-hero links readable against blue background The Claude license-options link added in the previous commit inherited the default `<a>` style (`var(--hp-primary)` blue), which renders as blue-on-blue and is unreadable inside the blue install-hero. Add a scoped `.install-hero a` rule that uses white with an underline (matching the existing lead-paragraph contrast pattern) so any link nested in the hero stays legible. * Reorder /home install flow: auto-mode is now Step 2, Agnes install becomes Step 3 Step 3 (was Step 2) pastes a ~20-command bash bootstrap into a fresh Claude Code session. Without auto-mode enabled first, each Bash/edit command needs a manual approve click — bad UX for first-time users. Move auto-mode from the outside-hero `<details>` reference block into the install-hero as a real Step 2, between "install Claude Code" and "install Agnes". Content is the persistent `acceptEdits` snippet (write to ~/.claude/settings.json) plus a one-liner pointing at Shift+Tab for users who are already inside a running Claude Code session. YOLO mode for full Bash auto-approve stays on /setup-advanced behind the existing link. The outside-hero `setup-collapsible[data-section="step3"]` block is dropped — auto-mode is no longer reference content, it's a real install step, and duplicating it would just diverge over time. Onboarded users no longer see the auto-mode block at all (consistent with Steps 1 + 3 also hiding post-onboarding). Completion banner copy updated: "Step 1, 2 & 3 done — Claude Code installed, auto-mode set, Agnes ready". Dashboard CTA partial and other templates don't reference step numbers for this flow, so no adaptation needed there. * Simplify /home Step 2 to Shift+Tab only — drop the JSON snippet Operator pointed out two issues with the prior Step 2: 1. The settings.json snippet is redundant. Claude Code's first Shift+Tab cycle to auto-accept mode already prompts the user whether to persist it as default — Claude writes the config itself, no manual file edit needed. 2. The snippet only showed the POSIX path `~/.claude/settings.json`, which doesn't translate to native Windows. Replace the snippet + copy button with a plain Shift+Tab instruction, explicitly call out the first-time "make this the default?" prompt, and note that Claude handles the config write itself — same flow on macOS / Linux / WSL / Windows. Adds a fallback line for users who already closed the post-OAuth session. * Tighten /home Step 2 install-note to two paragraphs Operator: drop the 'Claude writes the setting itself, so this works the same on macOS / Linux / WSL / Windows...' line plus the 'auto-approves file edits going forward; Bash commands stay gated — that's the safe default' line. Both were filler — the make-default prompt already implies persistence, and gated Bash is the obvious default users won't be surprised by. Result: paragraph 1 carries Shift+Tab + first-time make-default say-yes + closed-session fallback in one breath; paragraph 2 keeps the verbatim YOLO link. Same affordances, less vertical space.
2532 lines
82 KiB
HTML
2532 lines
82 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - {{ config.INSTANCE_NAME or 'Data Analyst Portal' }}{% endblock %}
|
|
|
|
{% block head_extra %}
|
|
{% if not config.THEME_FONT_URL %}
|
|
<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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
{% endif %}
|
|
<style>
|
|
/* ── Header ── */
|
|
.header {
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0 32px;
|
|
height: 72px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: 2px;
|
|
}
|
|
|
|
.header-logo {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
}
|
|
.header-logo svg {
|
|
display: block;
|
|
}
|
|
a.header-logo:focus-visible {
|
|
outline: 2px solid var(--primary);
|
|
outline-offset: 2px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.header-subtitle {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
letter-spacing: 0.4px;
|
|
text-transform: uppercase;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.header-email {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.avatar {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
background: var(--primary-light);
|
|
color: var(--primary);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.avatar-img {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
border: 2px solid var(--border);
|
|
}
|
|
|
|
.btn-logout {
|
|
font-family: var(--font-primary);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
background: none;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 6px 14px;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.btn-logout:hover {
|
|
color: var(--text-primary);
|
|
border-color: #D1D5DB;
|
|
background: var(--border-light);
|
|
}
|
|
|
|
/* ── Main Container ── */
|
|
.main {
|
|
max-width: 1280px;
|
|
margin: 0 auto;
|
|
padding: 28px 32px 48px;
|
|
}
|
|
|
|
/* ── Stats Row ── */
|
|
.stats-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr);
|
|
gap: 16px;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 20px 22px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
letter-spacing: -0.5px;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
/* ── Credit Line ── */
|
|
.credit-line {
|
|
text-align: center;
|
|
font-size: 12px;
|
|
color: #9CA3AF;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.credit-line .heart {
|
|
color: #EF4444;
|
|
}
|
|
|
|
.credit-line strong {
|
|
color: #6B7280;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* ── Two-Column Layout ── */
|
|
.dashboard-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 400px;
|
|
grid-template-rows: auto auto;
|
|
gap: 24px;
|
|
}
|
|
|
|
.left-column {
|
|
grid-row: 1 / 3;
|
|
display: grid;
|
|
grid-template-rows: subgrid;
|
|
gap: 24px;
|
|
}
|
|
|
|
.right-column {
|
|
grid-row: 1 / 3;
|
|
display: grid;
|
|
grid-template-rows: subgrid;
|
|
gap: 24px;
|
|
position: sticky;
|
|
top: 24px;
|
|
}
|
|
|
|
/* ── Card Base ── */
|
|
.card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card-header {
|
|
padding: 22px 24px 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.card-header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.card-body {
|
|
padding: 20px 24px 24px;
|
|
}
|
|
|
|
/* ── Section Title ── */
|
|
.section-title {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.6px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
/* ── Data Source Cards ── */
|
|
.data-source {
|
|
padding: 22px 24px;
|
|
border-bottom: 1px solid var(--border-light);
|
|
}
|
|
|
|
.data-source:last-of-type {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.data-source-header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.data-source-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
}
|
|
|
|
.data-source-icon {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 10px;
|
|
background: var(--border-light);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.data-source-icon svg {
|
|
color: #6B7280;
|
|
}
|
|
|
|
.data-source-icon.realtime {
|
|
background: rgba(16, 183, 127, 0.1);
|
|
}
|
|
|
|
.data-source-icon.realtime svg {
|
|
color: var(--success);
|
|
}
|
|
|
|
.data-source-icon.disabled {
|
|
background: #F3F4F6;
|
|
}
|
|
|
|
.data-source-icon.disabled svg {
|
|
color: #9CA3AF;
|
|
}
|
|
|
|
.data-source-name {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.data-source-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.status-dot {
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: 50%;
|
|
background: var(--success);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.status-dot--live {
|
|
animation: pulse-dot 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pulse-dot {
|
|
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(16, 183, 127, 0.4); }
|
|
50% { opacity: 0.8; box-shadow: 0 0 0 4px rgba(16, 183, 127, 0); }
|
|
}
|
|
|
|
.data-source-details {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
margin-left: 58px;
|
|
}
|
|
|
|
.badge-included {
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: #059669;
|
|
background: rgba(16, 183, 127, 0.1);
|
|
border-radius: 6px;
|
|
padding: 4px 10px;
|
|
white-space: nowrap;
|
|
align-self: center;
|
|
}
|
|
|
|
.badge-subscribed {
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--primary);
|
|
background: var(--primary-light);
|
|
border-radius: 6px;
|
|
padding: 4px 10px;
|
|
white-space: nowrap;
|
|
align-self: center;
|
|
}
|
|
|
|
.badge-disabled {
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: #9CA3AF;
|
|
background: #F3F4F6;
|
|
border-radius: 6px;
|
|
padding: 4px 10px;
|
|
white-space: nowrap;
|
|
align-self: center;
|
|
}
|
|
|
|
/* ── Toggles inside data source ── */
|
|
.data-source-toggles {
|
|
display: flex;
|
|
gap: 16px;
|
|
margin-left: 58px;
|
|
margin-top: 12px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--border-light);
|
|
}
|
|
|
|
.toggle-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.toggle-switch {
|
|
position: relative;
|
|
width: 36px;
|
|
height: 20px;
|
|
}
|
|
|
|
.toggle-switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.toggle-slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: #E5E7EB;
|
|
transition: .2s;
|
|
border-radius: 20px;
|
|
}
|
|
|
|
.toggle-slider:before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 14px;
|
|
width: 14px;
|
|
left: 3px;
|
|
bottom: 3px;
|
|
background-color: white;
|
|
transition: .2s;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
input:checked + .toggle-slider {
|
|
background-color: var(--primary);
|
|
}
|
|
|
|
input:checked + .toggle-slider:before {
|
|
transform: translateX(16px);
|
|
}
|
|
|
|
/* Disabled/locked toggle */
|
|
.toggle-switch.locked .toggle-slider {
|
|
cursor: not-allowed;
|
|
background-color: #9CA3AF;
|
|
}
|
|
|
|
.toggle-switch.locked .toggle-slider:before {
|
|
transform: translateX(16px);
|
|
}
|
|
|
|
input:disabled + .toggle-slider {
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.toggle-label {
|
|
font-size: 12px;
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.toggle-label.locked {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* ── Catalog CTA ── */
|
|
.catalog-cta {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 18px 24px;
|
|
border-top: 1px solid var(--border-light);
|
|
background: var(--primary-light);
|
|
border-radius: 0 0 12px 12px;
|
|
}
|
|
|
|
.catalog-cta-text {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.catalog-cta-link {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--primary);
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
transition: gap 0.15s ease;
|
|
}
|
|
|
|
.catalog-cta-link:hover {
|
|
gap: 10px;
|
|
}
|
|
|
|
/* ── Corporate Memory Widget ── */
|
|
.memory-card {
|
|
border-left: 3px solid var(--warning);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.memory-card .card-header {
|
|
background: rgba(245, 159, 10, 0.08);
|
|
padding: 22px 24px;
|
|
}
|
|
|
|
.memory-icon {
|
|
width: 28px;
|
|
height: 28px;
|
|
background: var(--warning);
|
|
border-radius: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.badge-beta {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
background: var(--border-light);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
padding: 2px 7px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.memory-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 12px;
|
|
padding: 20px 24px;
|
|
}
|
|
|
|
.memory-stat {
|
|
text-align: center;
|
|
padding: 14px 8px;
|
|
border-radius: 8px;
|
|
background: var(--border-light);
|
|
}
|
|
|
|
.memory-stat--highlight {
|
|
background: rgba(245, 159, 10, 0.1);
|
|
}
|
|
|
|
.memory-stat-value {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.memory-stat--highlight .memory-stat-value {
|
|
color: #D97706;
|
|
}
|
|
|
|
.memory-stat-label {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.memory-description {
|
|
margin: 0 24px 20px;
|
|
padding: 14px 16px;
|
|
border-left: 3px solid rgba(245, 159, 10, 0.4);
|
|
background: rgba(245, 159, 10, 0.04);
|
|
border-radius: 0 8px 8px 0;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
flex: 1;
|
|
}
|
|
|
|
.memory-description strong {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.memory-description code {
|
|
background: rgba(0, 0, 0, 0.06);
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.memory-footer {
|
|
padding: 16px 24px;
|
|
border-top: 1px solid var(--border-light);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-top: auto;
|
|
}
|
|
|
|
.memory-sync {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.memory-sync .sync-icon {
|
|
color: var(--success);
|
|
}
|
|
|
|
.btn-memory {
|
|
font-family: var(--font-primary);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #92400E;
|
|
background: rgba(245, 159, 10, 0.15);
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 8px 16px;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.btn-memory:hover {
|
|
background: rgba(245, 159, 10, 0.25);
|
|
}
|
|
|
|
/* ── Activity Center Widget ── */
|
|
.activity-card {
|
|
border-left: 3px solid #10B981;
|
|
}
|
|
|
|
.activity-card .card-header {
|
|
background: rgba(16, 185, 129, 0.08);
|
|
padding: 22px 24px;
|
|
}
|
|
|
|
.activity-icon {
|
|
width: 28px;
|
|
height: 28px;
|
|
background: #10B981;
|
|
border-radius: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.badge-demo {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: #92400E;
|
|
background: rgba(245, 159, 10, 0.15);
|
|
border: 1px solid rgba(245, 159, 10, 0.3);
|
|
border-radius: 4px;
|
|
padding: 2px 7px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.activity-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 12px;
|
|
padding: 20px 24px;
|
|
}
|
|
|
|
.activity-stat {
|
|
text-align: center;
|
|
padding: 14px 8px;
|
|
border-radius: 8px;
|
|
background: var(--border-light);
|
|
}
|
|
|
|
.activity-stat--highlight {
|
|
background: rgba(16, 185, 129, 0.1);
|
|
}
|
|
|
|
.activity-stat-value {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.activity-stat--highlight .activity-stat-value {
|
|
color: #059669;
|
|
}
|
|
|
|
.activity-stat-label {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.activity-maturity-bar {
|
|
margin: 0 24px 16px;
|
|
}
|
|
|
|
.activity-maturity-label {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.maturity-bar {
|
|
display: flex;
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
gap: 2px;
|
|
}
|
|
|
|
.maturity-segment {
|
|
transition: flex 0.3s ease;
|
|
}
|
|
|
|
.maturity-segment--optimized { background: #10B981; }
|
|
.maturity-segment--mature { background: #3B82F6; }
|
|
.maturity-segment--developing { background: #F59E0B; }
|
|
.maturity-segment--early { background: #9CA3AF; }
|
|
|
|
.maturity-legend {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-top: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.maturity-legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 10px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.maturity-legend-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.activity-description {
|
|
margin: 0 24px 20px;
|
|
padding: 14px 16px;
|
|
border-left: 3px solid rgba(16, 185, 129, 0.4);
|
|
background: rgba(16, 185, 129, 0.04);
|
|
border-radius: 0 8px 8px 0;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.activity-description strong {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.activity-footer {
|
|
padding: 16px 24px;
|
|
border-top: 1px solid var(--border-light);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.activity-trend {
|
|
font-size: 12px;
|
|
color: #059669;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.btn-activity {
|
|
font-family: var(--font-primary);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #065F46;
|
|
background: rgba(16, 185, 129, 0.15);
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 8px 16px;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.btn-activity:hover {
|
|
background: rgba(16, 185, 129, 0.25);
|
|
}
|
|
|
|
/* ── Bottom Cards Row ── */
|
|
.bottom-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px;
|
|
}
|
|
|
|
/* ── Notifications Card ── */
|
|
.notif-channels {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.notif-channel {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 16px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
background: var(--surface);
|
|
}
|
|
|
|
.notif-channel-icon {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 8px;
|
|
background: var(--border-light);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.notif-channel-icon.telegram {
|
|
background: rgba(25, 118, 210, 0.1);
|
|
color: #1976D2;
|
|
}
|
|
|
|
.notif-channel-icon.desktop {
|
|
background: rgba(124, 58, 237, 0.1);
|
|
color: #7C3AED;
|
|
}
|
|
|
|
.notif-channel-icon svg {
|
|
color: inherit;
|
|
}
|
|
|
|
.notif-channel-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.notif-status {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.notif-status--active {
|
|
color: var(--success);
|
|
}
|
|
|
|
.notif-status--inactive {
|
|
color: #9CA3AF;
|
|
}
|
|
|
|
.link-manage {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--primary);
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.link-manage:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.link-manage.active {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.notif-badge {
|
|
margin-left: auto;
|
|
flex-shrink: 0;
|
|
padding: 4px 10px;
|
|
border-radius: 6px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.notif-badge.active {
|
|
background: rgba(16, 183, 127, 0.1);
|
|
color: #059669;
|
|
}
|
|
|
|
.notif-unlink {
|
|
display: none;
|
|
margin-left: auto;
|
|
flex-shrink: 0;
|
|
padding: 4px 10px;
|
|
border-radius: 6px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
background: rgba(239, 68, 68, 0.1);
|
|
color: #EF4444;
|
|
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.notif-unlink:hover {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
}
|
|
|
|
.notif-unlink.visible,
|
|
.notif-managing .notif-unlink {
|
|
display: inline-block;
|
|
}
|
|
|
|
.notif-managing .notif-badge {
|
|
display: none;
|
|
}
|
|
|
|
.notif-link {
|
|
display: none;
|
|
margin-left: auto;
|
|
flex-shrink: 0;
|
|
padding: 4px 10px;
|
|
border-radius: 6px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
background: rgba(37, 99, 235, 0.1);
|
|
color: #2563EB;
|
|
border: 1px solid rgba(37, 99, 235, 0.2);
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.notif-link:hover {
|
|
background: rgba(37, 99, 235, 0.2);
|
|
}
|
|
|
|
.notif-managing .notif-link {
|
|
display: inline-block;
|
|
}
|
|
|
|
.telegram-verify {
|
|
display: none;
|
|
margin-top: 12px;
|
|
padding: 12px;
|
|
background: var(--background);
|
|
border-radius: 8px;
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.telegram-verify.visible {
|
|
display: block;
|
|
}
|
|
|
|
.telegram-verify ol {
|
|
margin: 8px 0 12px 18px;
|
|
line-height: 1.8;
|
|
}
|
|
|
|
.telegram-verify-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
|
|
.telegram-verify-row input {
|
|
width: 120px;
|
|
padding: 6px 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
text-align: center;
|
|
}
|
|
|
|
.telegram-verify-row button {
|
|
padding: 6px 14px;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
background: var(--primary);
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.telegram-verify-row button:hover {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.telegram-verify-error {
|
|
color: var(--error);
|
|
font-size: 11px;
|
|
margin-top: 6px;
|
|
display: none;
|
|
}
|
|
|
|
/* ── Account Card ── */
|
|
.account-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
}
|
|
|
|
.account-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
}
|
|
|
|
.account-label {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.4px;
|
|
width: 90px;
|
|
flex-shrink: 0;
|
|
padding-top: 2px;
|
|
}
|
|
|
|
.account-value {
|
|
font-size: 14px;
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.account-value--mono {
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.badge-role {
|
|
display: inline-block;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
border-radius: 4px;
|
|
padding: 3px 10px;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.badge-role.admin {
|
|
color: #EA580C;
|
|
background: rgba(234, 88, 12, 0.1);
|
|
}
|
|
|
|
.badge-role.privileged {
|
|
color: #7C3AED;
|
|
background: rgba(124, 58, 237, 0.1);
|
|
}
|
|
|
|
.badge-role.analyst {
|
|
color: var(--primary);
|
|
background: var(--primary-light);
|
|
}
|
|
|
|
.badge-role.default {
|
|
color: var(--text-secondary);
|
|
background: var(--border-light);
|
|
}
|
|
|
|
.badge-group {
|
|
display: inline-block;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
font-family: var(--font-mono);
|
|
color: var(--text-secondary);
|
|
background: var(--border-light);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
padding: 2px 8px;
|
|
margin-right: 6px;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.account-scripts {
|
|
list-style: none;
|
|
padding: 0;
|
|
flex: 1;
|
|
}
|
|
|
|
.account-scripts li {
|
|
font-size: 13px;
|
|
color: var(--text-primary);
|
|
padding: 6px 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.account-scripts li + li {
|
|
border-top: 1px solid var(--border-light);
|
|
}
|
|
|
|
.script-name {
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.script-time {
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.cron-line {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
margin-top: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.cron-line svg {
|
|
width: 12px;
|
|
height: 12px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.account-empty {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
font-style: italic;
|
|
}
|
|
|
|
.account-hint {
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
opacity: 0.7;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.account-hint code {
|
|
background: var(--background);
|
|
padding: 1px 5px;
|
|
border-radius: 3px;
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
}
|
|
|
|
.sync-datasets {
|
|
display: flex;
|
|
gap: 4px;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.dataset-badge {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
background: rgba(16, 183, 127, 0.1);
|
|
color: var(--success);
|
|
border-radius: 4px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* ── Setup Banner ── */
|
|
/* ── Environment Setup CTA (shown after account creation) ── */
|
|
.env-setup-cta {
|
|
background: linear-gradient(135deg, #0073D1 0%, #0056A3 100%);
|
|
border-radius: 12px;
|
|
padding: 24px 32px;
|
|
margin-bottom: 24px;
|
|
box-shadow: 0 4px 16px rgba(0, 115, 209, 0.2);
|
|
color: white;
|
|
}
|
|
|
|
.env-setup-cta h3 {
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
margin: 0 0 4px 0;
|
|
color: white;
|
|
}
|
|
|
|
.env-setup-cta .env-subtitle {
|
|
font-size: 13px;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.env-setup-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.env-setup-row .code-pill {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
padding: 8px 16px;
|
|
border-radius: 6px;
|
|
font-family: var(--font-mono);
|
|
font-size: 14px;
|
|
color: white;
|
|
}
|
|
|
|
.env-setup-row .btn-setup {
|
|
font-family: var(--font-primary);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--primary);
|
|
background: #FFFFFF;
|
|
border: none;
|
|
border-radius: 6px;
|
|
padding: 8px 20px;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
white-space: nowrap;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.env-setup-row .btn-setup:hover {
|
|
background: #F0F7FF;
|
|
}
|
|
|
|
.env-setup-row .btn-setup.copied {
|
|
background: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
.env-setup-row .env-meta {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.env-setup-row .env-hint {
|
|
font-size: 12px;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
}
|
|
|
|
.env-setup-link {
|
|
font-size: 13px;
|
|
color: rgba(255, 255, 255, 0.9);
|
|
text-decoration: underline;
|
|
text-underline-offset: 2px;
|
|
}
|
|
.env-setup-link:hover { color: #fff; }
|
|
|
|
.setup-link-banner {
|
|
margin-top: 12px;
|
|
padding: 10px 16px;
|
|
font-size: 13px;
|
|
color: var(--text-secondary, #6b7280);
|
|
text-align: center;
|
|
}
|
|
.setup-link-banner a {
|
|
color: var(--primary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
.setup-link-banner a:hover { text-decoration: underline; }
|
|
|
|
.env-setup-cta .btn-setup[disabled] {
|
|
opacity: 0.7;
|
|
cursor: wait;
|
|
}
|
|
/* .setup-error + .setup-fallback-* styles now live in
|
|
templates/_claude_setup_cta.jinja so /home reuses them. */
|
|
|
|
.btn-setup-secondary {
|
|
font-family: var(--font-primary);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--primary);
|
|
background: transparent;
|
|
border: 1px solid var(--primary);
|
|
border-radius: 6px;
|
|
padding: 6px 16px;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn-setup-secondary:hover {
|
|
background: rgba(0, 115, 209, 0.05);
|
|
}
|
|
|
|
.btn-setup-secondary.copied {
|
|
background: var(--success);
|
|
color: white;
|
|
border-color: var(--success);
|
|
}
|
|
|
|
/* ── Support Banner ── */
|
|
.support-banner {
|
|
text-align: center;
|
|
padding: 16px;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.support-banner .heart {
|
|
color: #EF4444;
|
|
}
|
|
|
|
.slack-badge {
|
|
display: inline-block;
|
|
margin-left: 8px;
|
|
padding: 2px 8px;
|
|
background: var(--background);
|
|
border-radius: 4px;
|
|
color: var(--text-primary);
|
|
text-decoration: none;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.slack-badge:hover {
|
|
background: var(--border);
|
|
}
|
|
|
|
/* ── Footer ── */
|
|
.footer {
|
|
text-align: center;
|
|
padding: 32px;
|
|
font-size: 12px;
|
|
color: #9CA3AF;
|
|
}
|
|
|
|
/* ── Flash Messages ── */
|
|
.flash-messages {
|
|
max-width: 1280px;
|
|
margin: 0 auto;
|
|
padding: 16px 32px 0;
|
|
}
|
|
|
|
.flash {
|
|
padding: 12px 16px;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.flash-success {
|
|
background: rgba(16, 183, 127, 0.1);
|
|
color: var(--success);
|
|
border-left: 3px solid var(--success);
|
|
}
|
|
|
|
.flash-error {
|
|
background: rgba(234, 88, 12, 0.1);
|
|
color: var(--error);
|
|
border-left: 3px solid var(--error);
|
|
}
|
|
|
|
/* ── New User Layout ── */
|
|
.new-user-grid {
|
|
max-width: 960px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.setup-card {
|
|
padding: 32px 36px;
|
|
}
|
|
|
|
.setup-card h3 {
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin: 0;
|
|
}
|
|
|
|
.setup-header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.setup-header .setup-subtitle {
|
|
color: var(--text-secondary);
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* ── Onboarding sections ── */
|
|
.onboard-section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.onboard-section:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.onboard-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.onboard-label .step-num {
|
|
width: 22px;
|
|
height: 22px;
|
|
background: var(--primary);
|
|
color: white;
|
|
border-radius: 50%;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.onboard-label strong {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.onboard-label .step-hint {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
margin-left: auto;
|
|
}
|
|
|
|
/* ── Terminal block (steps 1-3 combined) ── */
|
|
.terminal-block {
|
|
background: #1e1e2e;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.terminal-bar {
|
|
background: #313244;
|
|
padding: 6px 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.terminal-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.terminal-dot.r { background: #f38ba8; }
|
|
.terminal-dot.y { background: #f9e2af; }
|
|
.terminal-dot.g { background: #a6e3a1; }
|
|
|
|
.terminal-lines {
|
|
padding: 12px 16px;
|
|
}
|
|
|
|
.terminal-line {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 4px 0;
|
|
color: #cdd6f4;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.terminal-line .prompt {
|
|
color: #a6e3a1;
|
|
margin-right: 8px;
|
|
user-select: none;
|
|
}
|
|
|
|
.terminal-line .cmd {
|
|
flex: 1;
|
|
}
|
|
|
|
.terminal-line .comment {
|
|
color: #6c7086;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.terminal-line .btn-copy-term {
|
|
padding: 2px 6px;
|
|
background: transparent;
|
|
border: 1px solid #45475a;
|
|
color: #6c7086;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
font-size: 11px;
|
|
font-family: var(--font-mono);
|
|
margin-left: 8px;
|
|
transition: all 0.15s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.terminal-line .btn-copy-term:hover {
|
|
border-color: #89b4fa;
|
|
color: #89b4fa;
|
|
}
|
|
|
|
.terminal-line .btn-copy-term.copied {
|
|
border-color: #a6e3a1;
|
|
color: #a6e3a1;
|
|
}
|
|
|
|
/* ── Registration inline (step 4) ── */
|
|
.reg-inline {
|
|
background: var(--background);
|
|
border-radius: 8px;
|
|
padding: 16px 20px;
|
|
}
|
|
|
|
.reg-inline .reg-row {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.reg-inline .reg-field {
|
|
flex: 1;
|
|
}
|
|
|
|
.reg-inline .reg-field label {
|
|
display: block;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.reg-inline .reg-field textarea {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
resize: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.reg-inline .reg-field textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
box-shadow: 0 0 0 2px rgba(0, 115, 209, 0.1);
|
|
}
|
|
|
|
.reg-inline .reg-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-top: 8px;
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.reg-inline .username-tag {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
color: var(--primary);
|
|
background: rgba(0, 115, 209, 0.08);
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.reg-inline .btn-register {
|
|
padding: 8px 20px;
|
|
background: var(--primary);
|
|
color: white;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.reg-inline .btn-register:hover {
|
|
background: #005BA3;
|
|
}
|
|
|
|
/* ── Claude Code section (step 5) ── */
|
|
.claude-section {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.claude-section .code-inline {
|
|
background: var(--background);
|
|
padding: 8px 14px;
|
|
border-radius: 6px;
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn-copy-v2 {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 18px;
|
|
background: var(--primary);
|
|
color: white;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.btn-copy-v2:hover {
|
|
background: #005BA3;
|
|
}
|
|
|
|
.btn-copy-v2.copied {
|
|
background: var(--success);
|
|
}
|
|
|
|
.claude-hint {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
margin-left: auto;
|
|
}
|
|
|
|
.helper-text {
|
|
margin-top: 24px;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* ── Registration Card ── */
|
|
.registration-card h3 {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin: 0 0 16px 0;
|
|
}
|
|
|
|
.info-box-v2 {
|
|
background: var(--background);
|
|
padding: 12px 16px;
|
|
border-radius: 6px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.username-preview {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
color: var(--primary);
|
|
margin-left: 8px;
|
|
}
|
|
|
|
.form-v2 {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.form-group-v2 label {
|
|
display: block;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.form-group-v2 textarea {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.form-group-v2 textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
box-shadow: 0 0 0 2px rgba(0, 115, 209, 0.1);
|
|
}
|
|
|
|
.form-row-v2 {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.form-info-v2 {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.form-info-v2 code {
|
|
background: var(--background);
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
font-family: var(--font-mono);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn-v2 {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 8px 16px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
text-decoration: none;
|
|
border: none;
|
|
}
|
|
|
|
.btn-primary-v2 {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary-v2:hover {
|
|
background: #005BA3;
|
|
}
|
|
|
|
.help-text-v2 {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* ── Alert ── */
|
|
.alert-v2 {
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
gap: 16px;
|
|
}
|
|
|
|
.alert-error-v2 {
|
|
background: rgba(234, 88, 12, 0.1);
|
|
border-left: 3px solid var(--error);
|
|
}
|
|
|
|
.alert-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
background: var(--error);
|
|
color: white;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 600;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.alert-v2 h4 {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin: 0 0 8px 0;
|
|
}
|
|
|
|
.alert-v2 p {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
margin: 4px 0;
|
|
}
|
|
|
|
.beta-badge {
|
|
display: inline-block;
|
|
padding: 2px 7px;
|
|
border-radius: 4px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
background: #F3F4F6;
|
|
color: #9CA3AF;
|
|
border: 1px solid #E5E7EB;
|
|
vertical-align: middle;
|
|
margin-left: 6px;
|
|
}
|
|
|
|
/* ── Responsive ── */
|
|
@media (max-width: 1100px) {
|
|
.dashboard-grid {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: auto;
|
|
}
|
|
|
|
.left-column,
|
|
.right-column {
|
|
grid-row: auto;
|
|
grid-template-rows: auto;
|
|
position: static;
|
|
}
|
|
|
|
.stats-row {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 700px) {
|
|
.header {
|
|
padding: 0 16px;
|
|
}
|
|
|
|
.main {
|
|
padding: 20px 16px 40px;
|
|
}
|
|
|
|
.stats-row {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.bottom-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.onboard-label {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.onboard-label .step-hint {
|
|
width: 100%;
|
|
margin-left: 30px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.claude-section {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.claude-hint {
|
|
width: 100%;
|
|
margin-left: 0;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block layout %}
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
<div class="flash-messages">
|
|
{% for category, message in messages %}
|
|
<div class="flash flash-{{ category }}">
|
|
{{ message }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{% if user_info.exists %}
|
|
{# ── EXISTING USER ── #}
|
|
|
|
<main class="main">
|
|
|
|
{% if not account_details or not account_details.last_sync_display %}
|
|
<!-- ═══════════════ ENVIRONMENT SETUP CTA ═══════════════ -->
|
|
<div class="env-setup-cta">
|
|
<h3>Set up a new Claude Code</h3>
|
|
<p class="env-subtitle">Generates a personal access token and copies a ready-to-paste setup script to your clipboard. Paste into Claude Code to finish.</p>
|
|
<div class="env-setup-row">
|
|
<button type="button" onclick="setupNewClaude(this)" class="btn-setup" id="setupClaudeBtn">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
</svg>
|
|
Setup a new Claude Code
|
|
</button>
|
|
<span class="env-meta">
|
|
<span class="env-hint">Valid 90 days · token stays in clipboard only</span>
|
|
<a href="/setup" class="env-setup-link">Open the full setup page →</a>
|
|
</span>
|
|
</div>
|
|
<div id="setupClaudeError" class="setup-error" role="alert" style="display:none;"></div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ═══════════════ STATS ROW ═══════════════ -->
|
|
<div class="stats-row">
|
|
<div class="stat-card">
|
|
<span class="stat-label">Tables</span>
|
|
<span class="stat-value">{{ data_stats.tables }}</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-label">Columns</span>
|
|
<span class="stat-value">{{ data_stats.columns }}</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-label">Rows</span>
|
|
<span class="stat-value">{{ data_stats.rows_display }}</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-label">Data Size</span>
|
|
<span class="stat-value">{{ data_stats.size_display }}</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-label">Unstructured</span>
|
|
<span class="stat-value">{{ data_stats.unstructured_display }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Credit line -->
|
|
<div class="credit-line">
|
|
{{ config.INSTANCE_NAME }}
|
|
</div>
|
|
|
|
<!-- ═══════════════ DASHBOARD GRID ═══════════════ -->
|
|
<div class="dashboard-grid">
|
|
|
|
<!-- ── Left Column ── -->
|
|
<div class="left-column">
|
|
|
|
<!-- Your Data Card -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<span class="section-title" style="margin-bottom: 0;">Your Data</span>
|
|
</div>
|
|
|
|
<!-- Core Business Data -->
|
|
<div class="data-source">
|
|
<div class="data-source-header">
|
|
<div class="data-source-info">
|
|
<div class="data-source-icon">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
|
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="data-source-name">Core Business Data</div>
|
|
<div class="data-source-status">
|
|
<span class="status-dot{% if data_stats.last_updated %} status-dot--live{% endif %}"></span>
|
|
{% if data_stats.last_updated %}Synced {{ data_stats.last_updated }}{% else %}Not yet synced{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<span class="badge-included">Always included</span>
|
|
</div>
|
|
<div class="data-source-details">
|
|
{% if catalog_data %}{% for cat in catalog_data %}{{ cat.name }} ({{ cat.count }} tables){% if not loop.last %}, {% endif %}{% endfor %}{% if data_stats.remote_tables %} · {{ data_stats.local_tables }} local, {{ data_stats.remote_tables }} live{% endif %}{% else %}{{ data_stats.total_tables or data_stats.tables }} tables total{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if metrics_data %}
|
|
{% set metrics_total = namespace(n=0) %}
|
|
{% for c in metrics_data %}{% set metrics_total.n = metrics_total.n + c.metrics|length %}{% endfor %}
|
|
<div class="data-source">
|
|
<div class="data-source-header">
|
|
<div class="data-source-info">
|
|
<div class="data-source-icon">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M3 3v18h18"/>
|
|
<path d="M18.7 8l-5.1 5.2-2.8-2.7L7 14.3"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="data-source-name">Business Metrics</div>
|
|
<div class="data-source-status">
|
|
{{ metrics_total.n }} metrics across {{ metrics_data|length }} categories{% if data_stats.last_updated %} · data from {{ data_stats.last_updated }}{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<span class="badge-included">Always included</span>
|
|
</div>
|
|
<div class="data-source-details">
|
|
{% for c in metrics_data %}{{ c.label }} ({{ c.metrics|length }}){% if not loop.last %}, {% endif %}{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="catalog-cta">
|
|
<div class="catalog-cta-text">Manage your data subscriptions or discover new data sources</div>
|
|
<a href="{{ url_for('catalog') }}" class="catalog-cta-link">
|
|
Open Data Catalog
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="9 18 15 12 9 6"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Row: Notifications + Account -->
|
|
<div class="bottom-row">
|
|
|
|
<!-- Notifications Card -->
|
|
<div class="card">
|
|
<div class="card-header" style="padding-bottom: 0;">
|
|
<span class="card-title">Notifications</span>
|
|
<a class="link-manage" onclick="toggleNotifManage(this)">Manage</a>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="notif-channels">
|
|
<div class="notif-channel">
|
|
<div class="notif-channel-icon telegram">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M22 2L11 13"/>
|
|
<path d="M22 2L15 22L11 13L2 9L22 2Z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="notif-channel-name">Telegram</div>
|
|
{% if not telegram_status.linked %}
|
|
<span class="notif-status notif-status--inactive">Not linked</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if telegram_status.linked %}
|
|
<span class="notif-badge active">Active</span>
|
|
<button class="notif-unlink" onclick="unlinkChannel('telegram')">Unlink</button>
|
|
{% else %}
|
|
<button class="notif-link" onclick="showTelegramVerify()">Link</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="notif-channel">
|
|
<div class="notif-channel-icon desktop">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
|
|
<line x1="8" y1="21" x2="16" y2="21"/>
|
|
<line x1="12" y1="17" x2="12" y2="21"/>
|
|
</svg>
|
|
</div>
|
|
<div style="min-width: 0;">
|
|
<div class="notif-channel-name">macOS App</div>
|
|
<span class="beta-badge" style="margin-left: 0;">private beta</span>
|
|
{% if not desktop_status.linked %}
|
|
<span class="notif-status notif-status--inactive">Not linked</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if desktop_status.linked %}
|
|
<span class="notif-badge active">Active</span>
|
|
<button class="notif-unlink" onclick="unlinkChannel('desktop')">Unlink</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div id="telegramVerify" class="telegram-verify">
|
|
<ol>
|
|
<li>Message <code>/start</code> to <strong>@{{ config.TELEGRAM_BOT_USERNAME or 'your-bot' }}</strong> on Telegram</li>
|
|
<li>Enter the verification code below</li>
|
|
</ol>
|
|
<div class="telegram-verify-row">
|
|
<input type="text" id="verifyCode" placeholder="6-digit code" maxlength="6">
|
|
<button onclick="verifyTelegram()" id="verifyBtn">Verify</button>
|
|
</div>
|
|
<div id="telegramError" class="telegram-verify-error"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Account Card -->
|
|
<div class="card">
|
|
<div class="card-header" style="padding-bottom: 0;">
|
|
<span class="card-title">Account</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="account-grid">
|
|
<div class="account-row">
|
|
<span class="account-label">Username</span>
|
|
<span class="account-value account-value--mono">{{ username }}</span>
|
|
</div>
|
|
<div class="account-row">
|
|
<span class="account-label">Role</span>
|
|
{% if user_info.is_admin %}
|
|
<span class="badge-role admin">Administrator</span>
|
|
{% elif user_info.is_privileged %}
|
|
<span class="badge-role privileged">Privileged Analyst</span>
|
|
{% elif user_info.is_analyst %}
|
|
<span class="badge-role analyst">Standard Analyst</span>
|
|
{% else %}
|
|
<span class="badge-role default">User</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="account-row">
|
|
<span class="account-label">Groups</span>
|
|
<div>
|
|
{% for group in user_info.groups %}
|
|
<span class="badge-group">{{ group }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if account_details %}
|
|
<div class="account-row">
|
|
<span class="account-label">Scripts</span>
|
|
<div style="flex: 1;">
|
|
{% if account_details.notification_scripts %}
|
|
<ul class="account-scripts">
|
|
{% for script in account_details.notification_scripts %}
|
|
<li>
|
|
<span class="script-name">{{ script.stem }}</span>
|
|
<span class="script-time">{{ script.last_run or 'never' }}</span>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% if account_details.cron_schedule %}
|
|
<div class="cron-line">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
{{ account_details.cron_schedule }}
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="account-empty">No scripts configured</div>
|
|
<div class="account-hint">Add <code>.py</code> scripts to <code>~/user/notifications/</code></div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="account-row">
|
|
<span class="account-label">Last Sync</span>
|
|
<div>
|
|
{% if account_details.last_sync_display %}
|
|
<span class="account-value" style="font-size: 13px; color: var(--text-secondary);">{{ account_details.last_sync_display }}</span>
|
|
{% else %}
|
|
<div class="account-empty">Not yet synced</div>
|
|
<div class="account-hint">Run <code>bash server/scripts/sync_data.sh</code></div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /bottom-row -->
|
|
</div><!-- /left-column -->
|
|
|
|
<!-- ── Right Column ── -->
|
|
<div class="right-column">
|
|
|
|
{# Memory is admin-only. Widget hidden for non-admin; route
|
|
/corporate-memory + nav link are likewise admin-gated. #}
|
|
{% if session.user.is_admin %}
|
|
<!-- Corporate Memory Widget -->
|
|
<div class="card memory-card">
|
|
<div class="card-header">
|
|
<div class="card-header-left">
|
|
<div class="memory-icon">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
<path d="M2 17l10 5 10-5"/>
|
|
<path d="M2 12l10 5 10-5"/>
|
|
</svg>
|
|
</div>
|
|
<span class="card-title">Corporate Memory</span>
|
|
</div>
|
|
<span class="badge-beta">private beta</span>
|
|
</div>
|
|
|
|
<div class="memory-stats">
|
|
<div class="memory-stat">
|
|
<div class="memory-stat-value" id="memoryContributors">0</div>
|
|
<div class="memory-stat-label">Contributors</div>
|
|
</div>
|
|
<div class="memory-stat">
|
|
<div class="memory-stat-value" id="memoryKnowledgeCount">0</div>
|
|
<div class="memory-stat-label">Knowledge Items</div>
|
|
</div>
|
|
<div class="memory-stat memory-stat--highlight">
|
|
<div class="memory-stat-value" id="memoryYourRules">0</div>
|
|
<div class="memory-stat-label">Your Rules</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="memory-description">
|
|
<strong>Shared knowledge</strong> from your team's Claude Code sessions.
|
|
Upvote useful insights and they'll sync to your local <code>.claude/rules/</code>.
|
|
</div>
|
|
|
|
<div class="memory-footer">
|
|
<div class="memory-sync">
|
|
<span class="status-dot"></span>
|
|
<span id="memorySyncStatus">Synced</span>
|
|
</div>
|
|
<a href="{{ url_for('corporate_memory') }}" class="btn-memory">
|
|
Browse Knowledge
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M9 18l6-6-6-6"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Activity Center Widget -->
|
|
<div class="card activity-card">
|
|
<div class="card-header">
|
|
<div class="card-header-left">
|
|
<div class="activity-icon">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="18" y1="20" x2="18" y2="10"/>
|
|
<line x1="12" y1="20" x2="12" y2="4"/>
|
|
<line x1="6" y1="20" x2="6" y2="14"/>
|
|
</svg>
|
|
</div>
|
|
<span class="card-title">Activity Center</span>
|
|
</div>
|
|
<span class="badge-demo">demo</span>
|
|
</div>
|
|
|
|
<div class="activity-stats">
|
|
<div class="activity-stat">
|
|
<div class="activity-stat-value">{{ activity_summary.get('teams_active', 0) }}/{{ activity_summary.get('teams_total', 0) }}</div>
|
|
<div class="activity-stat-label">Teams Active</div>
|
|
</div>
|
|
<div class="activity-stat">
|
|
<div class="activity-stat-value">{{ activity_summary.get('business_processes_identified', 0) }}</div>
|
|
<div class="activity-stat-label">Processes</div>
|
|
</div>
|
|
<div class="activity-stat activity-stat--highlight">
|
|
<div class="activity-stat-value">{{ activity_summary.get('avg_success_rate', 0) }}%</div>
|
|
<div class="activity-stat-label">Success Rate</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% set maturity = activity_summary.get('maturity_distribution', {}) %}
|
|
{% set maturity_total = maturity.get('optimized', 0) + maturity.get('mature', 0) + maturity.get('developing', 0) + maturity.get('early', 0) %}
|
|
<div class="activity-maturity-bar">
|
|
<div class="activity-maturity-label">Process Maturity Distribution</div>
|
|
{% if maturity_total > 0 %}
|
|
<div class="maturity-bar">
|
|
<div class="maturity-segment maturity-segment--optimized" style="flex: {{ maturity.get('optimized', 0) }}" title="Optimized: {{ maturity.get('optimized', 0) }}"></div>
|
|
<div class="maturity-segment maturity-segment--mature" style="flex: {{ maturity.get('mature', 0) }}" title="Mature: {{ maturity.get('mature', 0) }}"></div>
|
|
<div class="maturity-segment maturity-segment--developing" style="flex: {{ maturity.get('developing', 0) }}" title="Developing: {{ maturity.get('developing', 0) }}"></div>
|
|
<div class="maturity-segment maturity-segment--early" style="flex: {{ maturity.get('early', 0) }}" title="Early: {{ maturity.get('early', 0) }}"></div>
|
|
</div>
|
|
<div class="maturity-legend">
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot" style="background: #10B981"></span> Optimized ({{ maturity.get('optimized', 0) }})</span>
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot" style="background: #3B82F6"></span> Mature ({{ maturity.get('mature', 0) }})</span>
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot" style="background: #F59E0B"></span> Developing ({{ maturity.get('developing', 0) }})</span>
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot" style="background: #9CA3AF"></span> Early ({{ maturity.get('early', 0) }})</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="activity-description">
|
|
<strong>Strategic overview</strong> of how data powers business processes across {{ activity_summary.get('teams_total', 0) }} teams.
|
|
</div>
|
|
|
|
<div class="activity-footer">
|
|
<span class="activity-trend">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/>
|
|
<polyline points="17 6 23 6 23 12"/>
|
|
</svg>
|
|
{{ activity_summary.get('adoption_trend', '') }} adoption
|
|
</span>
|
|
<a href="{{ url_for('activity_center') }}" class="btn-activity">
|
|
View Activity Center
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M9 18l6-6-6-6"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /right-column -->
|
|
</div><!-- /dashboard-grid -->
|
|
|
|
{% if account_details and account_details.last_sync_display %}
|
|
<div class="setup-link-banner">
|
|
Need to set up another machine? <a href="/setup">Open the setup page →</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</main>
|
|
|
|
{% else %}
|
|
{# ── NEW USER ── #}
|
|
|
|
<main class="main">
|
|
<div class="new-user-grid">
|
|
{% if not username_available %}
|
|
<div class="alert-v2 alert-error-v2" style="margin-bottom: 16px;">
|
|
<span class="alert-icon">!</span>
|
|
<div>
|
|
<h4>Username Not Available</h4>
|
|
<p>{{ username_error }}</p>
|
|
<p>Your email generates username <code>{{ username }}</code>, which cannot be used. Contact an administrator.</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="card setup-card">
|
|
<div class="setup-header">
|
|
<h3>Get Started</h3>
|
|
<span class="setup-subtitle">Set up your workspace in 4 steps</span>
|
|
</div>
|
|
|
|
{# ── Steps 1-3: Terminal commands ── #}
|
|
<div class="onboard-section">
|
|
<div class="onboard-label">
|
|
<span class="step-num">1</span>
|
|
<strong>Create folder</strong>
|
|
<span class="step-num" style="margin-left: 12px;">2</span>
|
|
<strong>Generate SSH key</strong>
|
|
<span class="step-num" style="margin-left: 12px;">3</span>
|
|
<strong>Copy public key</strong>
|
|
<span class="step-hint">Run these in your terminal</span>
|
|
</div>
|
|
<div class="terminal-block">
|
|
<div class="terminal-bar">
|
|
<span class="terminal-dot r"></span>
|
|
<span class="terminal-dot y"></span>
|
|
<span class="terminal-dot g"></span>
|
|
</div>
|
|
<div class="terminal-lines">
|
|
<div class="terminal-line">
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">mkdir -p {{ project_dir }} && cd {{ project_dir }}</span>
|
|
<button onclick="copyTermLine(this, 'mkdir -p {{ project_dir }} && cd {{ project_dir }}')" class="btn-copy-term">copy</button>
|
|
</div>
|
|
<div class="terminal-line">
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">ssh-keygen -t ed25519 -f {{ ssh_key }} -N ''</span>
|
|
<button onclick="copyTermLine(this, "ssh-keygen -t ed25519 -f {{ ssh_key }} -N ''")" class="btn-copy-term">copy</button>
|
|
</div>
|
|
<div class="terminal-line">
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">cat {{ ssh_key }}.pub</span>
|
|
<button onclick="copyTermLine(this, 'cat {{ ssh_key }}.pub')" class="btn-copy-term">copy</button>
|
|
</div>
|
|
<div class="terminal-line">
|
|
<span class="comment"># Copy the output above and paste it below</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# ── Step 4: Register ── #}
|
|
{% if username_available %}
|
|
<div class="onboard-section">
|
|
<div class="onboard-label">
|
|
<span class="step-num">4</span>
|
|
<strong>Create your account</strong>
|
|
<span class="step-hint">
|
|
Username: <span class="username-tag" style="font-family: var(--font-mono); font-weight: 600; color: var(--primary);">{{ username }}</span>
|
|
</span>
|
|
</div>
|
|
<div class="reg-inline">
|
|
<form action="{{ url_for('register') }}" method="post">
|
|
<div class="reg-row">
|
|
<div class="reg-field">
|
|
<label for="ssh_key">Paste your SSH public key</label>
|
|
<textarea name="ssh_key" id="ssh_key" rows="1"
|
|
placeholder="ssh-ed25519 AAAA... or ssh-rsa AAAA..." required></textarea>
|
|
</div>
|
|
<button type="submit" class="btn-register">Create Account</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Step 5 (Claude Code setup) appears on dashboard after account creation #}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="support-banner" style="margin-top: 16px;">
|
|
{{ config.INSTANCE_NAME }} - need help? Contact your platform team.
|
|
</div>
|
|
</main>
|
|
{% endif %}
|
|
|
|
<footer class="footer">
|
|
<p>© {{ now().year if now is defined else 2024 }} {{ config.INSTANCE_COPYRIGHT or 'AI Data Analyst' }}</p>
|
|
</footer>
|
|
{# Setup-CTA JS (copyToClipboard, defaultTokenName, showSetupFallback,
|
|
setupNewClaude) + the SETUP_INSTRUCTIONS_TEMPLATE renderer all live
|
|
in the shared partial below. Both /dashboard and /home include it
|
|
so the clipboard payload, error handling, and fallback modal stay
|
|
in lockstep. The partial exposes copyToClipboard on window so the
|
|
copyCode / copyTermLine helpers below resolve it via global scope. #}
|
|
{% include "_claude_setup_cta.jinja" %}
|
|
|
|
<script>
|
|
function copyCode(button, text) {
|
|
var originalHTML = button.innerHTML;
|
|
copyToClipboard(text).then(function() {
|
|
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
|
button.classList.add('copied');
|
|
setTimeout(function() {
|
|
button.innerHTML = originalHTML;
|
|
button.classList.remove('copied');
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
function copyTermLine(button, text) {
|
|
copyToClipboard(text).then(function() {
|
|
button.textContent = 'done';
|
|
button.classList.add('copied');
|
|
setTimeout(function() {
|
|
button.textContent = 'copy';
|
|
button.classList.remove('copied');
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
async function updateSyncSettings() {
|
|
const jiraToggle = document.getElementById('toggle-jira');
|
|
const attachmentsToggle = document.getElementById('toggle-jira_attachments');
|
|
|
|
// Handle dependency: jira_attachments requires jira
|
|
if (!jiraToggle.checked) {
|
|
attachmentsToggle.checked = false;
|
|
attachmentsToggle.disabled = true;
|
|
} else {
|
|
attachmentsToggle.disabled = false;
|
|
}
|
|
|
|
const datasets = {
|
|
jira: jiraToggle.checked,
|
|
jira_attachments: attachmentsToggle.checked
|
|
};
|
|
|
|
try {
|
|
const resp = await fetch('/api/sync-settings', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({datasets: datasets})
|
|
});
|
|
if (resp.ok) {
|
|
// Reload to reflect real-time section change
|
|
location.reload();
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to update settings:', e);
|
|
}
|
|
}
|
|
|
|
// Load Corporate Memory stats
|
|
async function loadMemoryStats() {
|
|
try {
|
|
const resp = await fetch('/api/memory/stats');
|
|
if (resp.ok) {
|
|
const data = await resp.json();
|
|
document.getElementById('memoryKnowledgeCount').textContent = data.total || 0;
|
|
const approved = (data.by_status && data.by_status.approved) || 0;
|
|
const mandatory = (data.by_status && data.by_status.mandatory) || 0;
|
|
document.getElementById('memoryContributors').textContent = approved + mandatory;
|
|
document.getElementById('memoryYourRules').textContent = mandatory || 0;
|
|
}
|
|
} catch (e) {
|
|
// Silently fail - widget will show defaults
|
|
console.log('Could not load memory stats:', e);
|
|
}
|
|
}
|
|
|
|
// Load memory stats on page load. Memory is admin-only, so non-admin
|
|
// visitors never see the widget — skip the fetch to avoid pointless
|
|
// 401s in their console.
|
|
{% if user_info.exists and session.user.is_admin %}
|
|
document.addEventListener('DOMContentLoaded', loadMemoryStats);
|
|
{% endif %}
|
|
|
|
// Notification management
|
|
function toggleNotifManage(link) {
|
|
const channels = link.closest('.card').querySelector('.notif-channels');
|
|
const managing = channels.classList.toggle('notif-managing');
|
|
link.textContent = managing ? 'Done' : 'Manage';
|
|
link.classList.toggle('active', managing);
|
|
// Hide verify panel when exiting manage mode
|
|
if (!managing) {
|
|
document.getElementById('telegramVerify').classList.remove('visible');
|
|
}
|
|
}
|
|
|
|
function showTelegramVerify() {
|
|
document.getElementById('telegramVerify').classList.toggle('visible');
|
|
}
|
|
|
|
async function verifyTelegram() {
|
|
const code = document.getElementById('verifyCode').value.trim();
|
|
const errorEl = document.getElementById('telegramError');
|
|
const btn = document.getElementById('verifyBtn');
|
|
errorEl.style.display = 'none';
|
|
|
|
if (!code || code.length !== 6) {
|
|
errorEl.textContent = 'Please enter a 6-digit code.';
|
|
errorEl.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Verifying...';
|
|
|
|
try {
|
|
const resp = await fetch('/api/telegram/verify', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({code: code})
|
|
});
|
|
const data = await resp.json();
|
|
|
|
if (resp.ok) {
|
|
location.reload();
|
|
} else {
|
|
errorEl.textContent = data.error || 'Verification failed.';
|
|
errorEl.style.display = 'block';
|
|
}
|
|
} catch (e) {
|
|
errorEl.textContent = 'Network error. Please try again.';
|
|
errorEl.style.display = 'block';
|
|
}
|
|
|
|
btn.disabled = false;
|
|
btn.textContent = 'Verify';
|
|
}
|
|
|
|
async function unlinkChannel(channel) {
|
|
const label = channel === 'telegram' ? 'Telegram' : 'macOS App';
|
|
if (!confirm(`Unlink ${label}? You will stop receiving notifications.`)) return;
|
|
try {
|
|
const resp = await fetch(`/api/${channel}/unlink`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'}
|
|
});
|
|
if (resp.ok) {
|
|
location.reload();
|
|
} else {
|
|
const data = await resp.json();
|
|
alert(data.error || 'Failed to unlink.');
|
|
}
|
|
} catch (e) {
|
|
console.error('Unlink failed:', e);
|
|
alert('Failed to unlink. Please try again.');
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|