Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
539 lines
18 KiB
JavaScript
539 lines
18 KiB
JavaScript
/**
|
|
* Tooltip Definitions
|
|
* Defines all tooltip content, positioning, and behavior for the onboarding system
|
|
*/
|
|
|
|
(function(window) {
|
|
'use strict';
|
|
|
|
/**
|
|
* Validate a tooltip definition
|
|
* @param {Object} tooltip - The tooltip definition to validate
|
|
* @returns {Object} { valid: boolean, errors: string[] }
|
|
*/
|
|
function validateTooltipDefinition(tooltip) {
|
|
const errors = [];
|
|
|
|
// Required fields
|
|
if (!tooltip.id || typeof tooltip.id !== 'string') {
|
|
errors.push('Tooltip must have a valid string id');
|
|
}
|
|
|
|
if (!tooltip.element) {
|
|
errors.push('Tooltip must have an element selector or HTMLElement');
|
|
}
|
|
|
|
if (!tooltip.popover || typeof tooltip.popover !== 'object') {
|
|
errors.push('Tooltip must have a popover object');
|
|
} else {
|
|
// Validate popover fields
|
|
if (!tooltip.popover.title || typeof tooltip.popover.title !== 'string') {
|
|
errors.push('Tooltip popover must have a valid string title');
|
|
}
|
|
|
|
if (!tooltip.popover.description || typeof tooltip.popover.description !== 'string') {
|
|
errors.push('Tooltip popover must have a valid string description');
|
|
}
|
|
|
|
// Validate position if provided
|
|
if (tooltip.popover.position) {
|
|
const validPositions = ['top', 'bottom', 'left', 'right', 'center'];
|
|
if (!validPositions.includes(tooltip.popover.position)) {
|
|
errors.push(`Invalid position: ${tooltip.popover.position}. Must be one of: ${validPositions.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Validate align if provided
|
|
if (tooltip.popover.align) {
|
|
const validAligns = ['start', 'center', 'end'];
|
|
if (!validAligns.includes(tooltip.popover.align)) {
|
|
errors.push(`Invalid align: ${tooltip.popover.align}. Must be one of: ${validAligns.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Validate showButtons if provided
|
|
if (tooltip.popover.showButtons && !Array.isArray(tooltip.popover.showButtons)) {
|
|
errors.push('showButtons must be an array');
|
|
}
|
|
|
|
// Validate callbacks if provided
|
|
const callbacks = ['onNext', 'onPrevious', 'onClose', 'onSetupNow', 'onLater'];
|
|
callbacks.forEach(callback => {
|
|
if (tooltip.popover[callback] && typeof tooltip.popover[callback] !== 'function') {
|
|
errors.push(`${callback} must be a function`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Validate condition if provided
|
|
if (tooltip.condition && typeof tooltip.condition !== 'function') {
|
|
errors.push('condition must be a function');
|
|
}
|
|
|
|
// Validate priority if provided
|
|
if (tooltip.priority !== undefined && typeof tooltip.priority !== 'number') {
|
|
errors.push('priority must be a number');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate an array of tooltip definitions
|
|
* @param {Array} tooltips - Array of tooltip definitions
|
|
* @returns {Object} { valid: boolean, errors: Object[] }
|
|
*/
|
|
function validateTooltipDefinitions(tooltips) {
|
|
if (!Array.isArray(tooltips)) {
|
|
return {
|
|
valid: false,
|
|
errors: [{ tooltip: null, errors: ['tooltips must be an array'] }]
|
|
};
|
|
}
|
|
|
|
const allErrors = [];
|
|
const ids = new Set();
|
|
|
|
tooltips.forEach((tooltip, index) => {
|
|
const validation = validateTooltipDefinition(tooltip);
|
|
|
|
if (!validation.valid) {
|
|
allErrors.push({
|
|
tooltip: tooltip.id || `index ${index}`,
|
|
errors: validation.errors
|
|
});
|
|
}
|
|
|
|
// Check for duplicate IDs
|
|
if (tooltip.id) {
|
|
if (ids.has(tooltip.id)) {
|
|
allErrors.push({
|
|
tooltip: tooltip.id,
|
|
errors: [`Duplicate tooltip ID: ${tooltip.id}`]
|
|
});
|
|
}
|
|
ids.add(tooltip.id);
|
|
}
|
|
});
|
|
|
|
return {
|
|
valid: allErrors.length === 0,
|
|
errors: allErrors
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Error handler for tooltip system
|
|
*/
|
|
class TooltipError extends Error {
|
|
constructor(message, tooltipId = null) {
|
|
super(message);
|
|
this.name = 'TooltipError';
|
|
this.tooltipId = tooltipId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle tooltip definition errors
|
|
* @param {Object} validation - Validation result
|
|
* @throws {TooltipError} If validation fails
|
|
*/
|
|
function handleValidationErrors(validation) {
|
|
if (!validation.valid) {
|
|
const errorMessages = validation.errors.map(e =>
|
|
`${e.tooltip}: ${e.errors.join(', ')}`
|
|
).join('\n');
|
|
|
|
console.error('[TooltipDefinitions] Validation errors:', errorMessages);
|
|
throw new TooltipError(`Tooltip validation failed:\n${errorMessages}`);
|
|
}
|
|
}
|
|
|
|
// Export to global scope
|
|
window.TooltipValidation = {
|
|
validateTooltipDefinition,
|
|
validateTooltipDefinitions,
|
|
handleValidationErrors,
|
|
TooltipError
|
|
};
|
|
|
|
console.log('[TooltipDefinitions] Validation module loaded');
|
|
|
|
})(window);
|
|
|
|
|
|
/**
|
|
* Tooltip Definitions Array
|
|
* Defines all tooltips for the onboarding tour
|
|
*/
|
|
const TOOLTIP_DEFINITIONS = [
|
|
// 1. Welcome
|
|
{
|
|
id: 'welcome',
|
|
element: '#brand',
|
|
popover: {
|
|
title: 'Welcome to DashCaddy!',
|
|
description: `
|
|
<p>Your unified control panel for <strong>Docker</strong>, <strong>Caddy</strong>, and <strong>DNS</strong> — all in one place.</p>
|
|
<p>This tour will walk you through every section so you know exactly where everything is.</p>
|
|
<p style="margin-top: 8px; font-size: 0.85rem; opacity: 0.7;">You can click your logo anytime to customize it.</p>
|
|
`,
|
|
position: 'bottom',
|
|
align: 'start',
|
|
showButtons: ['next'],
|
|
showProgress: true
|
|
},
|
|
priority: 1
|
|
},
|
|
|
|
// 2. Infrastructure row
|
|
{
|
|
id: 'infrastructure',
|
|
element: '.top',
|
|
popover: {
|
|
title: 'Infrastructure Overview',
|
|
description: `
|
|
<p>This top row shows the backbone of your setup:</p>
|
|
<ul>
|
|
<li><strong>DNS1 / DNS2 / DNS3</strong> — your Technitium DNS servers</li>
|
|
<li><strong>Internet</strong> — live connectivity check with packet indicators</li>
|
|
<li><strong>Auth</strong> — TOTP authentication status</li>
|
|
<li><strong>DashCA</strong> — your certificate authority for your custom domain</li>
|
|
</ul>
|
|
<p style="font-size: 0.85rem; opacity: 0.7;">These core services are always pinned here, separate from your app grid.</p>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 2
|
|
},
|
|
|
|
// 3. Service cards
|
|
{
|
|
id: 'service-cards',
|
|
element: '#cards',
|
|
popover: {
|
|
title: 'Your App Grid',
|
|
description: `
|
|
<p>Every app you deploy appears here as a card. Each one shows:</p>
|
|
<ul>
|
|
<li><strong>Status dot</strong> — green = online, red = offline (pulses when down)</li>
|
|
<li><strong>ON/OFF badge</strong> — current state at a glance</li>
|
|
<li><strong>Response time</strong> — how fast the service responds (color-coded)</li>
|
|
<li><strong>Uptime bar</strong> — historical uptime percentage</li>
|
|
</ul>
|
|
<p style="font-size: 0.85rem; opacity: 0.7;">Hover over a card to see action buttons: Logs, Restart, Update, Settings, and Open.</p>
|
|
`,
|
|
position: 'top',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 3
|
|
},
|
|
|
|
// 4. App Selector
|
|
{
|
|
id: 'app-selector',
|
|
element: '#add-service-btn',
|
|
popover: {
|
|
title: 'App Selector — Deploy in One Click',
|
|
description: `
|
|
<p>Browse <strong>50+ self-hosted apps</strong> organized by category:</p>
|
|
<ul>
|
|
<li>Media (Plex, Jellyfin, Emby, Overseerr)</li>
|
|
<li>Downloads (*arr stack, qBittorrent, Transmission)</li>
|
|
<li>Productivity (Nextcloud, Vaultwarden, Gitea)</li>
|
|
<li>Monitoring (Uptime Kuma, Grafana, Prometheus)</li>
|
|
</ul>
|
|
<p>Each app deploys with Docker, Caddy reverse proxy, and DNS — fully configured automatically.</p>
|
|
<p style="margin-top: 6px; padding: 6px 10px; border-radius: 6px; background: rgba(241,196,15,0.12); border: 1px solid rgba(241,196,15,0.25); font-size: 0.85rem;">
|
|
<strong style="color: #f1c40f;">★ Premium:</strong> <strong>Recipes</strong> let you deploy entire stacks (e.g., full media server) in one click with pre-wired configs.
|
|
</p>
|
|
`,
|
|
position: 'top',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 4,
|
|
condition: () => document.getElementById('add-service-btn') !== null
|
|
},
|
|
|
|
// 5. Smart Arr Connect
|
|
{
|
|
id: 'smart-arr',
|
|
element: '#arr-setup-btn',
|
|
popover: {
|
|
title: 'Smart Arr Connect',
|
|
description: `
|
|
<p>Automatically wire up your entire media stack:</p>
|
|
<ul>
|
|
<li>Detects running Arr services (Radarr, Sonarr, Prowlarr, etc.)</li>
|
|
<li>Connects them together with the right API keys</li>
|
|
<li>Links Plex/Jellyfin to Overseerr for request management</li>
|
|
</ul>
|
|
<p style="font-size: 0.85rem; opacity: 0.7;">No manual API key copying — DashCaddy handles it all.</p>
|
|
`,
|
|
position: 'top',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 5,
|
|
condition: () => document.getElementById('arr-setup-btn') !== null
|
|
},
|
|
|
|
// 6. Add App Manually (toolbar)
|
|
{
|
|
id: 'add-manual',
|
|
element: '#add-service',
|
|
popover: {
|
|
title: 'Add App Manually',
|
|
description: `
|
|
<p>Already have a service running? Add it to your dashboard manually.</p>
|
|
<p>You can add:</p>
|
|
<ul>
|
|
<li><strong>Docker containers</strong> — auto-detects running containers</li>
|
|
<li><strong>External services</strong> — any URL you want to monitor</li>
|
|
<li><strong>Test services</strong> — try the UI without deploying anything</li>
|
|
</ul>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 6
|
|
},
|
|
|
|
// 7. Theme
|
|
{
|
|
id: 'theme-selector',
|
|
element: '#theme',
|
|
popover: {
|
|
title: 'Themes',
|
|
description: `
|
|
<p>Switch between <strong>7 built-in themes</strong>:</p>
|
|
<p>Dark, Light, Blue, Nord, Dracula, Solarized Dark, and Solarized Light.</p>
|
|
<p>Your preference is saved automatically. You can also build custom themes with the Theme Builder (in Admin → settings).</p>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 7
|
|
},
|
|
|
|
// 8. Toolbar: Status section
|
|
{
|
|
id: 'toolbar-status',
|
|
element: '.tools-section[data-section="status"]',
|
|
popover: {
|
|
title: 'Status Tools',
|
|
description: `
|
|
<p>Click <strong>Status</strong> to expand these tools:</p>
|
|
<ul>
|
|
<li><strong>Monitor</strong> — live CPU, memory, and disk usage for every container</li>
|
|
<li><strong>Health</strong> — service health dashboard with uptime history and alerts</li>
|
|
<li><strong>Updates</strong> — check for and apply Docker image updates</li>
|
|
</ul>
|
|
<p style="font-size: 0.85rem; opacity: 0.7;">These sections remember whether you left them open or closed.</p>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 8
|
|
},
|
|
|
|
// 9. Toolbar: Tools section
|
|
{
|
|
id: 'toolbar-tools',
|
|
element: '.tools-section[data-section="tools"]',
|
|
popover: {
|
|
title: 'Operational Tools',
|
|
description: `
|
|
<p>Click <strong>Tools</strong> for day-to-day operations:</p>
|
|
<ul>
|
|
<li><strong>Logs</strong> — view error logs from your API server and services</li>
|
|
<li><strong>Alerts</strong> — configure notification rules (email, webhook, etc.)</li>
|
|
<li><strong>Audit</strong> — full audit trail of every action taken in DashCaddy</li>
|
|
</ul>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 9
|
|
},
|
|
|
|
// 10. Toolbar: Admin section
|
|
{
|
|
id: 'toolbar-admin',
|
|
element: '.tools-section[data-section="admin"]',
|
|
popover: {
|
|
title: 'Admin & Configuration',
|
|
description: `
|
|
<p>Click <strong>Admin</strong> for setup and maintenance:</p>
|
|
<ul>
|
|
<li><strong>Tokens</strong> — manage API keys for DNS and other integrations</li>
|
|
<li><strong>Export / Import</strong> — backup and restore your dashboard configuration</li>
|
|
<li><strong>Backup</strong> — full system backup and restore</li>
|
|
<li><strong>License</strong> — activate your license code to unlock premium features</li>
|
|
<li><strong>API</strong> — interactive API documentation (125+ endpoints)</li>
|
|
</ul>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 10
|
|
},
|
|
|
|
// 11. Weather widget
|
|
{
|
|
id: 'weather',
|
|
element: '#weather-widget',
|
|
popover: {
|
|
title: 'Weather Widget',
|
|
description: `
|
|
<p>Shows current conditions for your location. Click the <strong>gear icon</strong> to configure your city or switch temperature units.</p>
|
|
<p style="font-size: 0.85rem; opacity: 0.7;">Uses Open-Meteo — no API key required.</p>
|
|
`,
|
|
position: 'bottom',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 11,
|
|
condition: () => document.getElementById('weather-widget') !== null
|
|
},
|
|
|
|
// 12. Reload Caddy
|
|
{
|
|
id: 'reload-caddy',
|
|
element: '#reload-caddy-top',
|
|
popover: {
|
|
title: 'Reload Caddy',
|
|
description: `
|
|
<p>After changing Caddy configuration (adding reverse proxy rules, SSL settings, etc.), click here to apply the changes live.</p>
|
|
<p style="font-size: 0.85rem; opacity: 0.7;">This is a graceful reload — existing connections are not dropped.</p>
|
|
`,
|
|
position: 'bottom',
|
|
align: 'end',
|
|
showButtons: ['previous', 'next'],
|
|
showProgress: true
|
|
},
|
|
priority: 12,
|
|
condition: () => document.getElementById('reload-caddy-top') !== null
|
|
},
|
|
|
|
// 13. Finish
|
|
{
|
|
id: 'tour-complete',
|
|
element: '#brand',
|
|
popover: {
|
|
title: 'You\'re All Set!',
|
|
description: `
|
|
<p>That covers the essentials. A few tips to get the most out of DashCaddy:</p>
|
|
<ul>
|
|
<li><strong>Click any card</strong> to open that service directly</li>
|
|
<li><strong>Keyboard shortcuts</strong> — press <code>?</code> anytime to see them all</li>
|
|
<li><strong>DashCA</strong> — visit your CA page to install the root certificate on any device</li>
|
|
</ul>
|
|
<div style="margin-top: 10px; padding: 10px 12px; border-radius: 8px; background: linear-gradient(135deg, rgba(241,196,15,0.1), rgba(243,156,18,0.08)); border: 1px solid rgba(241,196,15,0.25);">
|
|
<p style="margin: 0 0 6px; font-weight: 600; color: #f1c40f;">★ Unlock Premium</p>
|
|
<p style="margin: 0; font-size: 0.85rem;">Premium adds powerful features for serious homelabbers:</p>
|
|
<ul style="margin: 6px 0 0; font-size: 0.85rem;">
|
|
<li><strong>Auto-Login SSO</strong> — sign into every app automatically, no more password juggling</li>
|
|
<li><strong>Recipes</strong> — deploy full stacks (media server, dev environment) in one click</li>
|
|
<li><strong>Docker Swarm</strong> — orchestrate multi-node clusters from your dashboard</li>
|
|
</ul>
|
|
<p style="margin: 8px 0 0; font-size: 0.82rem; opacity: 0.8;">Activate in <strong>Admin → License</strong></p>
|
|
</div>
|
|
<p style="margin-top: 10px; font-size: 0.85rem; opacity: 0.7;">You can restart this tour anytime from <strong>Admin → Help Tour</strong>.</p>
|
|
`,
|
|
position: 'bottom',
|
|
align: 'start',
|
|
showButtons: ['previous', 'close'],
|
|
showProgress: true
|
|
},
|
|
priority: 13
|
|
}
|
|
];
|
|
|
|
/**
|
|
* Get tooltip definitions
|
|
* @returns {Array} Array of tooltip definitions
|
|
*/
|
|
function getTooltipDefinitions() {
|
|
return TOOLTIP_DEFINITIONS;
|
|
}
|
|
|
|
/**
|
|
* Get a specific tooltip by ID
|
|
* @param {string} id - Tooltip ID
|
|
* @returns {Object|null} Tooltip definition or null if not found
|
|
*/
|
|
function getTooltipById(id) {
|
|
return TOOLTIP_DEFINITIONS.find(t => t.id === id) || null;
|
|
}
|
|
|
|
/**
|
|
* Get tooltips filtered by condition
|
|
* @returns {Array} Array of tooltips that pass their condition check
|
|
*/
|
|
function getActiveTooltips() {
|
|
return TOOLTIP_DEFINITIONS.filter(tooltip => {
|
|
if (tooltip.condition && typeof tooltip.condition === 'function') {
|
|
try {
|
|
return tooltip.condition();
|
|
} catch (error) {
|
|
console.error(`[TooltipDefinitions] Error evaluating condition for ${tooltip.id}:`, error);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get tooltips sorted by priority
|
|
* @returns {Array} Array of tooltips sorted by priority (ascending)
|
|
*/
|
|
function getSortedTooltips() {
|
|
const tooltips = getActiveTooltips();
|
|
return tooltips.sort((a, b) => {
|
|
const priorityA = a.priority || 999;
|
|
const priorityB = b.priority || 999;
|
|
return priorityA - priorityB;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get tooltips marked as new features
|
|
* @returns {Array} Array of tooltips marked with isNewFeature flag
|
|
*/
|
|
function getNewFeatureTooltips() {
|
|
const tooltips = getActiveTooltips();
|
|
return tooltips.filter(tooltip => tooltip.isNewFeature === true)
|
|
.sort((a, b) => {
|
|
const priorityA = a.priority || 999;
|
|
const priorityB = b.priority || 999;
|
|
return priorityA - priorityB;
|
|
});
|
|
}
|
|
|
|
// Export to global scope
|
|
window.TooltipDefinitions = {
|
|
TOOLTIP_DEFINITIONS,
|
|
getTooltipDefinitions,
|
|
getTooltipById,
|
|
getActiveTooltips,
|
|
getSortedTooltips,
|
|
getNewFeatureTooltips
|
|
};
|
|
|
|
console.log('[TooltipDefinitions] Definitions loaded:', TOOLTIP_DEFINITIONS.length, 'tooltips');
|
|
|