282 lines
8.6 KiB
PHP
282 lines
8.6 KiB
PHP
<?php // phpcs:ignoreFile
|
|
|
|
require __DIR__ . '/../session.php';
|
|
require __DIR__ . '/../userAccess.php';
|
|
|
|
$pageTitle = 'My Dashboard';
|
|
$pageSubtitle = 'Monitor the tags you care about';
|
|
$pageDescription = 'Your personal workspace for saved historian tags and quick-launch tools.';
|
|
$assetBasePath = '../';
|
|
$layoutReturnUrl = '../overview.php';
|
|
$layoutReturnLabel = 'Back to overview';
|
|
|
|
require __DIR__ . '/../includes/layout/header.php';
|
|
?>
|
|
|
|
<div class="app-content">
|
|
<section class="data-panel personal-dashboard-panel">
|
|
<header class="personal-dashboard-panel__header">
|
|
<div>
|
|
<h2>Personal dashboard</h2>
|
|
<p id="personalDashboardSummary">
|
|
Loading your saved widgets...
|
|
</p>
|
|
</div>
|
|
<div class="personal-dashboard-panel__actions">
|
|
<button
|
|
type="button"
|
|
class="button button--ghost"
|
|
id="personalDashboardRefresh"
|
|
>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div
|
|
id="personalDashboardStatus"
|
|
class="personal-dashboard__status personal-dashboard__status--loading"
|
|
role="status"
|
|
aria-live="polite"
|
|
>
|
|
Fetching saved selections...
|
|
</div>
|
|
|
|
<div id="personalDashboardGrid" class="personal-dashboard__grid" aria-live="polite"></div>
|
|
</section>
|
|
</div>
|
|
|
|
<script>
|
|
const personalDashboardState = {
|
|
dashboardKey: 'default',
|
|
widgets: [],
|
|
isLoading: false,
|
|
};
|
|
|
|
const summaryElement = document.getElementById('personalDashboardSummary');
|
|
const statusElement = document.getElementById('personalDashboardStatus');
|
|
const gridElement = document.getElementById('personalDashboardGrid');
|
|
const refreshButton = document.getElementById('personalDashboardRefresh');
|
|
|
|
function setStatus(message, type) {
|
|
const stateClass = type === 'error'
|
|
? 'personal-dashboard__status--error'
|
|
: type === 'empty'
|
|
? 'personal-dashboard__status--empty'
|
|
: 'personal-dashboard__status--loading';
|
|
|
|
statusElement.textContent = message;
|
|
statusElement.className = `personal-dashboard__status ${stateClass}`;
|
|
}
|
|
|
|
function formatInterval(ms) {
|
|
if (ms < 1000) {
|
|
return `${ms} ms`;
|
|
}
|
|
|
|
const seconds = ms / 1000;
|
|
if (seconds < 60) {
|
|
return `${seconds.toFixed(0)}s`;
|
|
}
|
|
|
|
const minutes = seconds / 60;
|
|
return `${minutes.toFixed(1)} min`;
|
|
}
|
|
|
|
function formatTimeWindow(minutes) {
|
|
if (minutes < 60) {
|
|
return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
|
}
|
|
|
|
const hours = (minutes / 60).toFixed(1);
|
|
return `${hours.replace(/\.0$/, '')} hour${hours !== '1' ? 's' : ''}`;
|
|
}
|
|
|
|
function buildLaunchUrl(widget) {
|
|
const base = '../trends/live/autochart.php';
|
|
const params = new URLSearchParams({
|
|
primary: widget.tag_name,
|
|
autostart: '1',
|
|
});
|
|
|
|
params.set('interval', widget.update_interval_ms);
|
|
|
|
if (widget.display_label) {
|
|
params.set('primary_label', widget.display_label);
|
|
}
|
|
|
|
if (widget.time_window_minutes) {
|
|
params.set('window', widget.time_window_minutes);
|
|
}
|
|
|
|
return `${base}?${params.toString()}`;
|
|
}
|
|
|
|
function renderWidgetCard(widget) {
|
|
const card = document.createElement('article');
|
|
card.className = 'personal-card';
|
|
|
|
const header = document.createElement('header');
|
|
header.className = 'personal-card__header';
|
|
|
|
const title = document.createElement('div');
|
|
title.className = 'personal-card__title';
|
|
|
|
const tagName = document.createElement('span');
|
|
tagName.className = 'personal-card__tag';
|
|
tagName.textContent = widget.display_label || widget.tag_name;
|
|
title.appendChild(tagName);
|
|
|
|
if (widget.display_label && widget.display_label !== widget.tag_name) {
|
|
const subtitle = document.createElement('span');
|
|
subtitle.className = 'personal-card__subtitle';
|
|
subtitle.textContent = widget.tag_name;
|
|
title.appendChild(subtitle);
|
|
}
|
|
|
|
header.appendChild(title);
|
|
|
|
if (widget.series_color) {
|
|
const swatch = document.createElement('span');
|
|
swatch.className = 'personal-card__swatch';
|
|
swatch.setAttribute('title', `Series color ${widget.series_color}`);
|
|
swatch.style.setProperty('--personal-series-color', widget.series_color);
|
|
header.appendChild(swatch);
|
|
}
|
|
|
|
card.appendChild(header);
|
|
|
|
const meta = document.createElement('dl');
|
|
meta.className = 'personal-card__meta';
|
|
|
|
const metaItems = [
|
|
['Widget type', widget.widget_type],
|
|
['Update interval', formatInterval(widget.update_interval_ms)],
|
|
['Time window', formatTimeWindow(widget.time_window_minutes)],
|
|
['Axis preference', widget.preferred_axis],
|
|
['Rollup', widget.rollup_function],
|
|
];
|
|
|
|
metaItems.forEach((item) => {
|
|
const [label, value] = item;
|
|
const dt = document.createElement('dt');
|
|
dt.textContent = label;
|
|
const dd = document.createElement('dd');
|
|
dd.textContent = value || '—';
|
|
meta.appendChild(dt);
|
|
meta.appendChild(dd);
|
|
});
|
|
|
|
if (widget.scale_min !== null || widget.scale_max !== null) {
|
|
const dt = document.createElement('dt');
|
|
dt.textContent = 'Manual scale';
|
|
const minValue = widget.scale_min !== null ? widget.scale_min : 'auto';
|
|
const maxValue = widget.scale_max !== null ? widget.scale_max : 'auto';
|
|
const dd = document.createElement('dd');
|
|
dd.textContent = `${minValue} → ${maxValue}`;
|
|
meta.appendChild(dt);
|
|
meta.appendChild(dd);
|
|
}
|
|
|
|
if (widget.threshold_low !== null || widget.threshold_high !== null) {
|
|
const dt = document.createElement('dt');
|
|
dt.textContent = 'Thresholds';
|
|
const lowValue = widget.threshold_low !== null ? widget.threshold_low : '—';
|
|
const highValue = widget.threshold_high !== null ? widget.threshold_high : '—';
|
|
const dd = document.createElement('dd');
|
|
dd.textContent = `${lowValue} / ${highValue}`;
|
|
meta.appendChild(dt);
|
|
meta.appendChild(dd);
|
|
}
|
|
|
|
card.appendChild(meta);
|
|
|
|
const actions = document.createElement('div');
|
|
actions.className = 'personal-card__actions';
|
|
|
|
const launchLink = document.createElement('a');
|
|
launchLink.className = 'button button--success';
|
|
launchLink.href = buildLaunchUrl(widget);
|
|
launchLink.target = '_blank';
|
|
launchLink.rel = 'noopener';
|
|
launchLink.textContent = 'Open live chart';
|
|
actions.appendChild(launchLink);
|
|
|
|
const details = document.createElement('div');
|
|
details.className = 'personal-card__details';
|
|
details.textContent = 'Future updates will let you pin tables, gauges, and alarms here.';
|
|
actions.appendChild(details);
|
|
|
|
card.appendChild(actions);
|
|
|
|
return card;
|
|
}
|
|
|
|
function renderDashboard() {
|
|
gridElement.innerHTML = '';
|
|
|
|
if (!personalDashboardState.widgets.length) {
|
|
setStatus('No saved widgets yet. Use the upcoming editor to add your favorite tags.', 'empty');
|
|
summaryElement.textContent = 'Nothing is saved for this dashboard yet.';
|
|
return;
|
|
}
|
|
|
|
setStatus(`${personalDashboardState.widgets.length} widget(s) loaded.`, 'complete');
|
|
summaryElement.textContent = `Showing ${personalDashboardState.widgets.length} saved widget(s).`;
|
|
|
|
personalDashboardState.widgets.forEach((widget) => {
|
|
gridElement.appendChild(renderWidgetCard(widget));
|
|
});
|
|
}
|
|
|
|
async function loadDashboard() {
|
|
if (personalDashboardState.isLoading) {
|
|
return;
|
|
}
|
|
|
|
personalDashboardState.isLoading = true;
|
|
setStatus('Fetching saved selections...', 'loading');
|
|
summaryElement.textContent = 'Loading your saved widgets...';
|
|
|
|
try {
|
|
const url = new URL(
|
|
'../data/personal/get_user_tags.php',
|
|
window.location.href
|
|
);
|
|
url.searchParams.set('dashboard', personalDashboardState.dashboardKey);
|
|
|
|
const response = await fetch(url.toString(), { cache: 'no-cache' });
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}`);
|
|
}
|
|
|
|
const payload = await response.json();
|
|
if (!payload.success) {
|
|
throw new Error(payload.error || 'Unable to load personal dashboard');
|
|
}
|
|
|
|
personalDashboardState.widgets = Array.isArray(payload.widgets)
|
|
? payload.widgets
|
|
: [];
|
|
|
|
renderDashboard();
|
|
} catch (error) {
|
|
console.error('Failed to load personal dashboard:', error);
|
|
setStatus(`Could not load widgets: ${error.message}`, 'error');
|
|
summaryElement.textContent = 'Personal dashboard failed to load.';
|
|
} finally {
|
|
personalDashboardState.isLoading = false;
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadDashboard();
|
|
});
|
|
|
|
refreshButton.addEventListener('click', () => {
|
|
loadDashboard();
|
|
});
|
|
</script>
|
|
|
|
<?php require __DIR__ . '/../includes/layout/footer.php'; ?>
|