botui/ui/suite/dashboards/dashboards.html

1437 lines
59 KiB
HTML

<div class="dashboards-container" id="dashboards-app">
<header class="dashboards-header">
<div class="header-title">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
class="header-icon"
>
<rect
x="3"
y="3"
width="7"
height="7"
rx="1"
stroke="currentColor"
stroke-width="2"
/>
<rect
x="14"
y="3"
width="7"
height="7"
rx="1"
stroke="currentColor"
stroke-width="2"
/>
<rect
x="3"
y="14"
width="7"
height="7"
rx="1"
stroke="currentColor"
stroke-width="2"
/>
<rect
x="14"
y="14"
width="7"
height="7"
rx="1"
stroke="currentColor"
stroke-width="2"
/>
</svg>
<h2 data-i18n="dashboards-title">Dashboards</h2>
</div>
<div class="header-actions">
<div class="search-box">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="11" cy="11" r="8" />
<path d="M21 21l-4.35-4.35" />
</svg>
<input
type="text"
id="dashboardSearch"
placeholder="Search dashboards..."
data-i18n-placeholder="dashboards-search-placeholder"
hx-get="/api/dashboards"
hx-trigger="keyup changed delay:300ms"
hx-target="#dashboards-grid"
hx-include="this"
name="search"
/>
</div>
<select
id="dashboardFilter"
class="filter-select"
hx-get="/api/dashboards"
hx-trigger="change"
hx-target="#dashboards-grid"
hx-include="#dashboardSearch"
name="tag"
>
<option value="" data-i18n="dashboards-filter-all">
All Dashboards
</option>
<option value="sales" data-i18n="dashboards-filter-sales">
Sales
</option>
<option
value="marketing"
data-i18n="dashboards-filter-marketing"
>
Marketing
</option>
<option
value="operations"
data-i18n="dashboards-filter-operations"
>
Operations
</option>
<option value="finance" data-i18n="dashboards-filter-finance">
Finance
</option>
<option value="hr" data-i18n="dashboards-filter-hr">HR</option>
</select>
<button class="btn-primary" onclick="showCreateDashboardModal()">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
<span data-i18n="dashboards-create">New Dashboard</span>
</button>
</div>
</header>
<div class="dashboards-layout">
<aside class="dashboards-sidebar">
<div class="sidebar-section">
<h3 data-i18n="dashboards-my-dashboards">My Dashboards</h3>
<nav
class="dashboard-nav"
hx-get="/api/dashboards?owner=me"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-placeholder">
<div class="skeleton-item"></div>
<div class="skeleton-item"></div>
<div class="skeleton-item"></div>
</div>
</nav>
</div>
<div class="sidebar-section">
<h3 data-i18n="dashboards-shared">Shared With Me</h3>
<nav
class="dashboard-nav shared-nav"
hx-get="/api/dashboards?shared=true"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-placeholder">
<div class="skeleton-item"></div>
</div>
</nav>
</div>
<div class="sidebar-section">
<h3 data-i18n="dashboards-templates">Templates</h3>
<nav
class="dashboard-nav templates-nav"
hx-get="/api/dashboards/templates"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-placeholder">
<div class="skeleton-item"></div>
<div class="skeleton-item"></div>
</div>
</nav>
</div>
<div class="sidebar-section data-sources-section">
<h3 data-i18n="dashboards-data-sources">Data Sources</h3>
<div
class="data-sources-list"
hx-get="/api/dashboards/sources"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-placeholder">
<div class="skeleton-item small"></div>
<div class="skeleton-item small"></div>
</div>
</div>
<button
class="btn-link add-source-btn"
onclick="showAddDataSourceModal()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
<span data-i18n="dashboards-add-source"
>Add Data Source</span
>
</button>
</div>
</aside>
<main class="dashboards-main">
<div
class="dashboards-grid"
id="dashboards-grid"
hx-get="/api/dashboards"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="grid-loading">
<div class="dashboard-card-skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-body">
<div class="skeleton-chart"></div>
</div>
</div>
<div class="dashboard-card-skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-body">
<div class="skeleton-chart"></div>
</div>
</div>
<div class="dashboard-card-skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-body">
<div class="skeleton-chart"></div>
</div>
</div>
</div>
</div>
<div
class="dashboard-viewer"
id="dashboard-viewer"
style="display: none"
>
<div class="viewer-toolbar">
<button
class="btn-icon back-btn"
onclick="closeDashboardViewer()"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M19 12H5" />
<path d="M12 19l-7-7 7-7" />
</svg>
</button>
<h3 id="viewer-title">Dashboard</h3>
<div class="viewer-actions">
<select
id="viewerDateRange"
class="date-range-select"
onchange="updateDashboardData()"
>
<option value="today" data-i18n="dashboards-today">
Today
</option>
<option
value="7d"
selected
data-i18n="dashboards-last-7d"
>
Last 7 Days
</option>
<option value="30d" data-i18n="dashboards-last-30d">
Last 30 Days
</option>
<option value="90d" data-i18n="dashboards-last-90d">
Last 90 Days
</option>
<option value="1y" data-i18n="dashboards-last-year">
Last Year
</option>
<option
value="custom"
data-i18n="dashboards-custom-range"
>
Custom Range
</option>
</select>
<button
class="btn-icon"
onclick="refreshDashboard()"
title="Refresh"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"
/>
<path d="M21 3v5h-5" />
</svg>
</button>
<button
class="btn-icon"
onclick="editDashboard()"
title="Edit"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</svg>
</button>
<button
class="btn-icon"
onclick="shareDashboard()"
title="Share"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="18" cy="5" r="3" />
<circle cx="6" cy="12" r="3" />
<circle cx="18" cy="19" r="3" />
<line
x1="8.59"
y1="13.51"
x2="15.42"
y2="17.49"
/>
<line
x1="15.41"
y1="6.51"
x2="8.59"
y2="10.49"
/>
</svg>
</button>
<button
class="btn-icon"
onclick="exportDashboard()"
title="Export"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
/>
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
</button>
</div>
</div>
<div class="viewer-content" id="viewer-content"></div>
</div>
<div class="conversational-query" id="conversational-query">
<div class="query-header">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
<span data-i18n="dashboards-ask-data"
>Ask about your data</span
>
</div>
<div class="query-input-wrapper">
<input
type="text"
id="naturalLanguageQuery"
placeholder="e.g., Show me sales by region for last quarter..."
data-i18n-placeholder="dashboards-query-placeholder"
/>
<button
class="query-submit-btn"
hx-post="/api/dashboards/query"
hx-target="#query-results"
hx-include="#naturalLanguageQuery, #queryDataSource"
hx-indicator="#query-loading"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="22" y1="2" x2="11" y2="13" />
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
</button>
</div>
<select id="queryDataSource" class="query-source-select">
<option value="" data-i18n="dashboards-all-sources">
All Data Sources
</option>
</select>
<div id="query-loading" class="query-loading htmx-indicator">
<div class="loading-spinner small"></div>
<span data-i18n="dashboards-analyzing"
>Analyzing your data...</span
>
</div>
<div id="query-results" class="query-results"></div>
</div>
</main>
</div>
<div class="modal" id="createDashboardModal" style="display: none">
<div class="modal-backdrop" onclick="closeCreateDashboardModal()"></div>
<div class="modal-content">
<div class="modal-header">
<h3 data-i18n="dashboards-create-title">
Create New Dashboard
</h3>
<button
class="modal-close"
onclick="closeCreateDashboardModal()"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<form
id="createDashboardForm"
hx-post="/api/dashboards"
hx-target="#dashboards-grid"
hx-swap="innerHTML"
hx-on::after-request="closeCreateDashboardModal()"
>
<div class="form-group">
<label for="dashboardName" data-i18n="dashboards-name"
>Name</label
>
<input
type="text"
id="dashboardName"
name="name"
required
placeholder="My Dashboard"
data-i18n-placeholder="dashboards-name-placeholder"
/>
</div>
<div class="form-group">
<label
for="dashboardDescription"
data-i18n="dashboards-description"
>Description</label
>
<textarea
id="dashboardDescription"
name="description"
rows="3"
placeholder="Describe what this dashboard shows..."
data-i18n-placeholder="dashboards-description-placeholder"
></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label
for="dashboardLayout"
data-i18n="dashboards-layout"
>Layout</label
>
<select id="dashboardLayout" name="layout">
<option
value="grid-12"
data-i18n="dashboards-layout-12col"
>
12 Columns
</option>
<option
value="grid-6"
data-i18n="dashboards-layout-6col"
>
6 Columns
</option>
<option
value="grid-4"
data-i18n="dashboards-layout-4col"
>
4 Columns
</option>
</select>
</div>
<div class="form-group">
<label for="dashboardTags" data-i18n="dashboards-tags"
>Tags</label
>
<input
type="text"
id="dashboardTags"
name="tags"
placeholder="sales, marketing"
data-i18n-placeholder="dashboards-tags-placeholder"
/>
</div>
</div>
<div class="form-group checkbox-group">
<label>
<input
type="checkbox"
id="dashboardPublic"
name="is_public"
/>
<span data-i18n="dashboards-public"
>Make dashboard public</span
>
</label>
</div>
<div class="form-group template-selection">
<label data-i18n="dashboards-start-from">Start from</label>
<div class="template-options">
<button
type="button"
class="template-option active"
data-template="blank"
>
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
/>
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="8" y1="12" x2="16" y2="12" />
</svg>
<span data-i18n="dashboards-blank">Blank</span>
</button>
<button
type="button"
class="template-option"
data-template="sales"
>
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<line x1="12" y1="20" x2="12" y2="10" />
<line x1="18" y1="20" x2="18" y2="4" />
<line x1="6" y1="20" x2="6" y2="14" />
</svg>
<span data-i18n="dashboards-sales-template"
>Sales</span
>
</button>
<button
type="button"
class="template-option"
data-template="marketing"
>
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
/>
</svg>
<span data-i18n="dashboards-marketing-template"
>Marketing</span
>
</button>
<button
type="button"
class="template-option"
data-template="operations"
>
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
<span data-i18n="dashboards-operations-template"
>Operations</span
>
</button>
</div>
<input
type="hidden"
id="selectedTemplate"
name="template"
value="blank"
/>
</div>
<div class="modal-footer">
<button
type="button"
class="btn-secondary"
onclick="closeCreateDashboardModal()"
data-i18n="action-cancel"
>
Cancel
</button>
<button
type="submit"
class="btn-primary"
data-i18n="action-create"
>
Create Dashboard
</button>
</div>
</form>
</div>
</div>
<div class="modal" id="addDataSourceModal" style="display: none">
<div class="modal-backdrop" onclick="closeAddDataSourceModal()"></div>
<div class="modal-content">
<div class="modal-header">
<h3 data-i18n="dashboards-add-data-source">Add Data Source</h3>
<button class="modal-close" onclick="closeAddDataSourceModal()">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<form
id="addDataSourceForm"
hx-post="/api/dashboards/sources"
hx-target=".data-sources-list"
hx-swap="innerHTML"
hx-on::after-request="closeAddDataSourceModal()"
>
<div class="form-group">
<label for="sourceName" data-i18n="dashboards-source-name"
>Name</label
>
<input
type="text"
id="sourceName"
name="name"
required
placeholder="My Database"
data-i18n-placeholder="dashboards-source-name-placeholder"
/>
</div>
<div class="form-group">
<label for="sourceType" data-i18n="dashboards-source-type"
>Type</label
>
<select
id="sourceType"
name="source_type"
required
onchange="updateConnectionFields()"
>
<option value="" data-i18n="dashboards-select-type">
Select type...
</option>
<optgroup
label="Databases"
data-i18n-label="dashboards-databases"
>
<option value="postgresql">PostgreSQL</option>
<option value="mysql">MySQL</option>
<option value="sqlserver">SQL Server</option>
<option value="mongodb">MongoDB</option>
</optgroup>
<optgroup
label="Cloud Data Warehouses"
data-i18n-label="dashboards-warehouses"
>
<option value="bigquery">BigQuery</option>
<option value="snowflake">Snowflake</option>
<option value="redshift">Redshift</option>
</optgroup>
<optgroup
label="APIs"
data-i18n-label="dashboards-apis"
>
<option value="rest_api">REST API</option>
<option value="graphql_api">GraphQL API</option>
</optgroup>
<optgroup
label="Files"
data-i18n-label="dashboards-files"
>
<option value="csv">CSV</option>
<option value="excel">Excel</option>
<option value="google_sheets">Google Sheets</option>
</optgroup>
<optgroup
label="Internal"
data-i18n-label="dashboards-internal"
>
<option value="internal_tables">
Internal Tables
</option>
</optgroup>
</select>
</div>
<div id="connectionFields" class="connection-fields"></div>
<div class="form-group">
<label
for="sourceDescription"
data-i18n="dashboards-source-description"
>Description</label
>
<textarea
id="sourceDescription"
name="description"
rows="2"
placeholder="Optional description..."
data-i18n-placeholder="dashboards-source-description-placeholder"
></textarea>
</div>
<div class="modal-footer">
<button
type="button"
class="btn-secondary"
onclick="testDataSourceConnection()"
data-i18n="dashboards-test-connection"
>
Test Connection
</button>
<button
type="button"
class="btn-secondary"
onclick="closeAddDataSourceModal()"
data-i18n="action-cancel"
>
Cancel
</button>
<button
type="submit"
class="btn-primary"
data-i18n="action-add"
>
Add Source
</button>
</div>
</form>
</div>
</div>
<div class="modal" id="addWidgetModal" style="display: none">
<div class="modal-backdrop" onclick="closeAddWidgetModal()"></div>
<div class="modal-content modal-lg">
<div class="modal-header">
<h3 data-i18n="dashboards-add-widget">Add Widget</h3>
<button class="modal-close" onclick="closeAddWidgetModal()">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<div class="widget-type-grid">
<div class="widget-category">
<h4 data-i18n="dashboards-charts">Charts</h4>
<div class="widget-options">
<button
class="widget-option"
data-type="line_chart"
onclick="selectWidgetType('line_chart')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path d="M3 3v18h18" />
<path d="M7 16l4-4 4 4 5-6" />
</svg>
<span>Line Chart</span>
</button>
<button
class="widget-option"
data-type="bar_chart"
onclick="selectWidgetType('bar_chart')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<line x1="12" y1="20" x2="12" y2="10" />
<line x1="18" y1="20" x2="18" y2="4" />
<line x1="6" y1="20" x2="6" y2="14" />
</svg>
<span>Bar Chart</span>
</button>
<button
class="widget-option"
data-type="pie_chart"
onclick="selectWidgetType('pie_chart')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path d="M21.21 15.89A10 10 0 1 1 8 2.83" />
<path d="M22 12A10 10 0 0 0 12 2v10z" />
</svg>
<span>Pie Chart</span>
</button>
<button
class="widget-option"
data-type="area_chart"
onclick="selectWidgetType('area_chart')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path d="M3 3v18h18" />
<path
d="M7 16l4-4 4 4 5-6v10H7z"
fill="currentColor"
opacity="0.2"
/>
<path d="M7 16l4-4 4 4 5-6" />
</svg>
<span>Area Chart</span>
</button>
<button
class="widget-option"
data-type="scatter_plot"
onclick="selectWidgetType('scatter_plot')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path d="M3 3v18h18" />
<circle
cx="9"
cy="9"
r="2"
fill="currentColor"
/>
<circle
cx="15"
cy="7"
r="2"
fill="currentColor"
/>
<circle
cx="12"
cy="14"
r="2"
fill="currentColor"
/>
<circle
cx="18"
cy="12"
r="2"
fill="currentColor"
/>
</svg>
<span>Scatter Plot</span>
</button>
</div>
</div>
<div class="widget-category">
<h4 data-i18n="dashboards-data-display">Data Display</h4>
<div class="widget-options">
<button
class="widget-option"
data-type="kpi"
onclick="selectWidgetType('kpi')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
/>
<path d="M7 12h10M12 7v10" />
</svg>
<span>KPI Card</span>
</button>
<button
class="widget-option"
data-type="table"
onclick="selectWidgetType('table')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
/>
<line x1="3" y1="9" x2="21" y2="9" />
<line x1="3" y1="15" x2="21" y2="15" />
<line x1="9" y1="3" x2="9" y2="21" />
</svg>
<span>Table</span>
</button>
<button
class="widget-option"
data-type="gauge"
onclick="selectWidgetType('gauge')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path d="M12 2a10 10 0 0 0-7.07 17.07" />
<path d="M12 2a10 10 0 0 1 7.07 17.07" />
<circle cx="12" cy="12" r="2" />
<path d="M12 12l4-4" />
</svg>
<span>Gauge</span>
</button>
<button
class="widget-option"
data-type="map"
onclick="selectWidgetType('map')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<polygon
points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"
/>
<line x1="8" y1="2" x2="8" y2="18" />
<line x1="16" y1="6" x2="16" y2="22" />
</svg>
<span>Map</span>
</button>
</div>
</div>
<div class="widget-category">
<h4 data-i18n="dashboards-content">Content</h4>
<div class="widget-options">
<button
class="widget-option"
data-type="text"
onclick="selectWidgetType('text')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<polyline points="4 7 4 4 20 4 20 7" />
<line x1="9" y1="20" x2="15" y2="20" />
<line x1="12" y1="4" x2="12" y2="20" />
</svg>
<span>Text</span>
</button>
<button
class="widget-option"
data-type="image"
onclick="selectWidgetType('image')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
/>
<circle cx="8.5" cy="8.5" r="1.5" />
<polyline points="21 15 16 10 5 21" />
</svg>
<span>Image</span>
</button>
<button
class="widget-option"
data-type="iframe"
onclick="selectWidgetType('iframe')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
/>
<line x1="3" y1="9" x2="21" y2="9" />
<circle
cx="6"
cy="6"
r="1"
fill="currentColor"
/>
<circle
cx="9"
cy="6"
r="1"
fill="currentColor"
/>
</svg>
<span>Embed</span>
</button>
</div>
</div>
<div class="widget-category">
<h4 data-i18n="dashboards-filters">Filters</h4>
<div class="widget-options">
<button
class="widget-option"
data-type="filter"
onclick="selectWidgetType('filter')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<polygon
points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"
/>
</svg>
<span>Filter</span>
</button>
<button
class="widget-option"
data-type="date_range"
onclick="selectWidgetType('date_range')"
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<rect
x="3"
y="4"
width="18"
height="18"
rx="2"
ry="2"
/>
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
<span>Date Range</span>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn-secondary"
onclick="closeAddWidgetModal()"
data-i18n="action-cancel"
>
Cancel
</button>
</div>
</div>
</div>
</div>
<script>
let currentDashboardId = null;
let editMode = false;
function showCreateDashboardModal() {
document.getElementById("createDashboardModal").style.display = "flex";
}
function closeCreateDashboardModal() {
document.getElementById("createDashboardModal").style.display = "none";
document.getElementById("createDashboardForm").reset();
}
function showAddDataSourceModal() {
document.getElementById("addDataSourceModal").style.display = "flex";
}
function closeAddDataSourceModal() {
document.getElementById("addDataSourceModal").style.display = "none";
document.getElementById("addDataSourceForm").reset();
document.getElementById("connectionFields").innerHTML = "";
}
function showAddWidgetModal() {
document.getElementById("addWidgetModal").style.display = "flex";
}
function closeAddWidgetModal() {
document.getElementById("addWidgetModal").style.display = "none";
}
function selectWidgetType(type) {
closeAddWidgetModal();
if (currentDashboardId) {
htmx.ajax("POST", `/api/dashboards/${currentDashboardId}/widgets`, {
target: "#viewer-content",
swap: "innerHTML",
values: {
widget_type: type,
title: `New ${type.replace("_", " ")}`,
},
});
}
}
function openDashboard(id, name) {
currentDashboardId = id;
document.getElementById("viewer-title").textContent = name;
document.getElementById("dashboards-grid").style.display = "none";
document.getElementById("dashboard-viewer").style.display = "block";
htmx.ajax("GET", `/api/dashboards/${id}`, {
target: "#viewer-content",
swap: "innerHTML",
});
}
function closeDashboardViewer() {
currentDashboardId = null;
document.getElementById("dashboard-viewer").style.display = "none";
document.getElementById("dashboards-grid").style.display = "grid";
}
function refreshDashboard() {
if (currentDashboardId) {
htmx.ajax("GET", `/api/dashboards/${currentDashboardId}`, {
target: "#viewer-content",
swap: "innerHTML",
});
}
}
function updateDashboardData() {
const range = document.getElementById("viewerDateRange").value;
if (currentDashboardId) {
htmx.ajax(
"GET",
`/api/dashboards/${currentDashboardId}?range=${range}`,
{
target: "#viewer-content",
swap: "innerHTML",
},
);
}
}
function editDashboard() {
editMode = !editMode;
const viewer = document.getElementById("viewer-content");
if (editMode) {
viewer.classList.add("edit-mode");
showAddWidgetModal();
} else {
viewer.classList.remove("edit-mode");
}
}
function shareDashboard() {
if (currentDashboardId) {
const url = `${window.location.origin}/dashboards/${currentDashboardId}`;
navigator.clipboard.writeText(url).then(() => {
showNotification(
"Dashboard link copied to clipboard!",
"success",
);
});
}
}
function exportDashboard() {
if (currentDashboardId) {
window.open(
`/api/dashboards/${currentDashboardId}/export?format=pdf`,
"_blank",
);
}
}
function updateConnectionFields() {
const type = document.getElementById("sourceType").value;
const container = document.getElementById("connectionFields");
const dbFields = `
<div class="form-row">
<div class="form-group">
<label for="connHost" data-i18n="dashboards-host">Host</label>
<input type="text" id="connHost" name="connection.host" placeholder="localhost"/>
</div>
<div class="form-group">
<label for="connPort" data-i18n="dashboards-port">Port</label>
<input type="number" id="connPort" name="connection.port" placeholder="5432"/>
</div>
</div>
<div class="form-group">
<label for="connDatabase" data-i18n="dashboards-database">Database</label>
<input type="text" id="connDatabase" name="connection.database" required/>
</div>
<div class="form-row">
<div class="form-group">
<label for="connUsername" data-i18n="dashboards-username">Username</label>
<input type="text" id="connUsername" name="connection.username"/>
</div>
<div class="form-group">
<label for="connPassword" data-i18n="dashboards-password">Password</label>
<input type="password" id="connPassword" name="connection.password"/>
</div>
</div>
<div class="form-group checkbox-group">
<label>
<input type="checkbox" id="connSsl" name="connection.ssl"/>
<span data-i18n="dashboards-use-ssl">Use SSL</span>
</label>
</div>
`;
const apiFields = `
<div class="form-group">
<label for="connUrl" data-i18n="dashboards-api-url">API URL</label>
<input type="url" id="connUrl" name="connection.url" required placeholder="https://api.example.com"/>
</div>
<div class="form-group">
<label for="connApiKey" data-i18n="dashboards-api-key">API Key</label>
<input type="password" id="connApiKey" name="connection.api_key"/>
</div>
`;
const fileFields = `
<div class="form-group">
<label for="connUrl" data-i18n="dashboards-file-url">File URL or Path</label>
<input type="text" id="connUrl" name="connection.url" required placeholder="/path/to/file.csv or https://..."/>
</div>
`;
if (["postgresql", "mysql", "sqlserver", "mongodb"].includes(type)) {
container.innerHTML = dbFields;
} else if (["rest_api", "graphql_api"].includes(type)) {
container.innerHTML = apiFields;
} else if (["csv", "excel", "google_sheets"].includes(type)) {
container.innerHTML = fileFields;
} else if (["bigquery", "snowflake", "redshift"].includes(type)) {
container.innerHTML = `
<div class="form-group">
<label for="connString" data-i18n="dashboards-connection-string">Connection String</label>
<textarea id="connString" name="connection.connection_string" rows="3" required></textarea>
</div>
`;
} else {
container.innerHTML = "";
}
}
function testDataSourceConnection() {
const form = document.getElementById("addDataSourceForm");
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
htmx.ajax("POST", "/api/dashboards/sources/test", {
target: "#connectionFields",
swap: "beforeend",
values: data,
});
}
function showNotification(message, type) {
const notification = document.createElement("div");
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
document.querySelectorAll(".template-option").forEach((btn) => {
btn.addEventListener("click", function () {
document
.querySelectorAll(".template-option")
.forEach((b) => b.classList.remove("active"));
this.classList.add("active");
document.getElementById("selectedTemplate").value =
this.dataset.template;
});
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
closeCreateDashboardModal();
closeAddDataSourceModal();
closeAddWidgetModal();
if (currentDashboardId && !editMode) {
closeDashboardViewer();
}
}
});
</script>