nexus basic
This commit is contained in:
@@ -23,6 +23,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$config = [];
|
$config = [];
|
||||||
if ($itemType === 'page_module') {
|
if ($itemType === 'page_module') {
|
||||||
$config['page_module_id'] = (int) ($_POST['page_module_id'] ?? 0);
|
$config['page_module_id'] = (int) ($_POST['page_module_id'] ?? 0);
|
||||||
|
} elseif ($itemType === 'app') {
|
||||||
|
$config['app_id'] = (int) ($_POST['app_id'] ?? 0);
|
||||||
|
} elseif ($itemType === 'widget_template') {
|
||||||
|
$config['widget_template_id'] = (int) ($_POST['widget_template_id'] ?? 0);
|
||||||
|
} elseif ($itemType === 'bookmark_group') {
|
||||||
|
$config['bookmarks'] = trim((string) ($_POST['bookmarks'] ?? ''));
|
||||||
} else {
|
} else {
|
||||||
$config['url'] = trim((string) ($_POST['target_url'] ?? ''));
|
$config['url'] = trim((string) ($_POST['target_url'] ?? ''));
|
||||||
}
|
}
|
||||||
@@ -61,10 +67,20 @@ if ($currentDashboard === null) {
|
|||||||
$currentDashboardId = (int) ($currentDashboard['id'] ?? 0);
|
$currentDashboardId = (int) ($currentDashboard['id'] ?? 0);
|
||||||
$dashboardItems = $service->listItems($currentDashboardId);
|
$dashboardItems = $service->listItems($currentDashboardId);
|
||||||
$ownPageModules = $service->listPageModulesForOwner($ownerKey);
|
$ownPageModules = $service->listPageModulesForOwner($ownerKey);
|
||||||
|
$availableApps = $service->listApps($ownerKey, true);
|
||||||
|
$availableWidgetTemplates = $service->listWidgetTemplates($ownerKey, true);
|
||||||
$pageModuleMap = [];
|
$pageModuleMap = [];
|
||||||
foreach ($ownPageModules as $pageModule) {
|
foreach ($ownPageModules as $pageModule) {
|
||||||
$pageModuleMap[(int) ($pageModule['id'] ?? 0)] = $pageModule;
|
$pageModuleMap[(int) ($pageModule['id'] ?? 0)] = $pageModule;
|
||||||
}
|
}
|
||||||
|
$appMap = [];
|
||||||
|
foreach ($availableApps as $appEntry) {
|
||||||
|
$appMap[(int) ($appEntry['id'] ?? 0)] = $appEntry;
|
||||||
|
}
|
||||||
|
$widgetTemplateMap = [];
|
||||||
|
foreach ($availableWidgetTemplates as $template) {
|
||||||
|
$widgetTemplateMap[(int) ($template['id'] ?? 0)] = $template;
|
||||||
|
}
|
||||||
|
|
||||||
$GLOBALS['layout_header_base_title'] = 'Nexus';
|
$GLOBALS['layout_header_base_title'] = 'Nexus';
|
||||||
$GLOBALS['layout_header_title'] = 'Nexus';
|
$GLOBALS['layout_header_title'] = 'Nexus';
|
||||||
@@ -115,7 +131,7 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
|
|
||||||
<section class="section-box">
|
<section class="section-box">
|
||||||
<h2>Element hinzufügen</h2>
|
<h2>Element hinzufügen</h2>
|
||||||
<p class="muted">V1 unterstützt direkte Links, iFrames und gespeicherte Seitenmodule.</p>
|
<p class="muted">Verfügbar sind direkte Links, iFrames, persönliche Linklisten, globale Apps, Seitenmodule und wiederverwendbare Widgets.</p>
|
||||||
<form method="post" class="setup-form">
|
<form method="post" class="setup-form">
|
||||||
<input type="hidden" name="action" value="add_item">
|
<input type="hidden" name="action" value="add_item">
|
||||||
<input type="hidden" name="dashboard_id" value="<?= $currentDashboardId ?>">
|
<input type="hidden" name="dashboard_id" value="<?= $currentDashboardId ?>">
|
||||||
@@ -129,7 +145,10 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
<select name="item_type" data-dashboard-item-type>
|
<select name="item_type" data-dashboard-item-type>
|
||||||
<option value="link">Link</option>
|
<option value="link">Link</option>
|
||||||
<option value="iframe">iFrame</option>
|
<option value="iframe">iFrame</option>
|
||||||
|
<option value="bookmark_group">Linkliste</option>
|
||||||
|
<option value="app">App</option>
|
||||||
<option value="page_module">Seitenmodul</option>
|
<option value="page_module">Seitenmodul</option>
|
||||||
|
<option value="widget_template">Widget-Vorlage</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,6 +161,19 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
<span>Ziel-URL</span>
|
<span>Ziel-URL</span>
|
||||||
<input type="url" name="target_url" placeholder="https://...">
|
<input type="url" name="target_url" placeholder="https://...">
|
||||||
</label>
|
</label>
|
||||||
|
<label class="setup-field muted" data-dashboard-bookmarks hidden>
|
||||||
|
<span>Linkliste</span>
|
||||||
|
<textarea name="bookmarks" rows="5" placeholder="Bezeichnung | https://ziel.example Nexus | /dashboard"></textarea>
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted" data-dashboard-app hidden>
|
||||||
|
<span>App</span>
|
||||||
|
<select name="app_id">
|
||||||
|
<option value="0">Bitte wählen</option>
|
||||||
|
<?php foreach ($availableApps as $appEntry): ?>
|
||||||
|
<option value="<?= (int) ($appEntry['id'] ?? 0) ?>"><?= e((string) ($appEntry['name'] ?? 'App')) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="setup-field muted" data-dashboard-page-module hidden>
|
<label class="setup-field muted" data-dashboard-page-module hidden>
|
||||||
<span>Seitenmodul</span>
|
<span>Seitenmodul</span>
|
||||||
<select name="page_module_id">
|
<select name="page_module_id">
|
||||||
@@ -151,6 +183,15 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="setup-field muted" data-dashboard-widget-template hidden>
|
||||||
|
<span>Widget-Vorlage</span>
|
||||||
|
<select name="widget_template_id">
|
||||||
|
<option value="0">Bitte wählen</option>
|
||||||
|
<?php foreach ($availableWidgetTemplates as $template): ?>
|
||||||
|
<option value="<?= (int) ($template['id'] ?? 0) ?>"><?= e((string) ($template['name'] ?? 'Widget')) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setup-grid">
|
<div class="setup-grid">
|
||||||
<label class="setup-field muted">
|
<label class="setup-field muted">
|
||||||
@@ -189,6 +230,11 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
<?php
|
<?php
|
||||||
$itemType = (string) ($item['item_type'] ?? 'link');
|
$itemType = (string) ($item['item_type'] ?? 'link');
|
||||||
$config = is_array($item['config'] ?? null) ? $item['config'] : [];
|
$config = is_array($item['config'] ?? null) ? $item['config'] : [];
|
||||||
|
if ($itemType === 'widget_template' && !empty($config['widget_template_id']) && isset($widgetTemplateMap[(int) $config['widget_template_id']])) {
|
||||||
|
$template = $widgetTemplateMap[(int) $config['widget_template_id']];
|
||||||
|
$itemType = (string) ($template['widget_type'] ?? $itemType);
|
||||||
|
$config = array_merge(is_array($template['config'] ?? null) ? $template['config'] : [], $config);
|
||||||
|
}
|
||||||
$columnSpan = max(1, min(4, (int) ($item['column_span'] ?? 1)));
|
$columnSpan = max(1, min(4, (int) ($item['column_span'] ?? 1)));
|
||||||
$rowSpan = max(1, min(4, (int) ($item['row_span'] ?? 1)));
|
$rowSpan = max(1, min(4, (int) ($item['row_span'] ?? 1)));
|
||||||
$gridStyles = 'grid-column: span ' . $columnSpan . '; grid-row: span ' . $rowSpan . ';';
|
$gridStyles = 'grid-column: span ' . $columnSpan . '; grid-row: span ' . $rowSpan . ';';
|
||||||
@@ -202,10 +248,30 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
if ($itemType === 'page_module' && !empty($config['page_module_id'])) {
|
if ($itemType === 'page_module' && !empty($config['page_module_id'])) {
|
||||||
$pageModule = $pageModuleMap[(int) $config['page_module_id']] ?? null;
|
$pageModule = $pageModuleMap[(int) $config['page_module_id']] ?? null;
|
||||||
}
|
}
|
||||||
|
$appEntry = null;
|
||||||
|
if ($itemType === 'app' && !empty($config['app_id'])) {
|
||||||
|
$appEntry = $appMap[(int) $config['app_id']] ?? null;
|
||||||
|
}
|
||||||
$targetUrl = trim((string) ($config['url'] ?? ''));
|
$targetUrl = trim((string) ($config['url'] ?? ''));
|
||||||
if ($pageModule !== null) {
|
if ($pageModule !== null) {
|
||||||
$targetUrl = trim((string) ($pageModule['target_url'] ?? $targetUrl));
|
$targetUrl = trim((string) ($pageModule['target_url'] ?? $targetUrl));
|
||||||
}
|
}
|
||||||
|
if ($appEntry !== null) {
|
||||||
|
$targetUrl = trim((string) ($appEntry['app_url'] ?? $targetUrl));
|
||||||
|
}
|
||||||
|
$bookmarks = [];
|
||||||
|
if ($itemType === 'bookmark_group') {
|
||||||
|
foreach (preg_split('/\r\n|\r|\n/', trim((string) ($config['bookmarks'] ?? ''))) ?: [] as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
if ($line === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$label, $url] = array_pad(array_map('trim', explode('|', $line, 2)), 2, '');
|
||||||
|
if ($label !== '' && $url !== '') {
|
||||||
|
$bookmarks[] = ['label' => $label, 'url' => $url];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<article class="card-box dashboard-widget" style="<?= e($gridStyles) ?>">
|
<article class="card-box dashboard-widget" style="<?= e($gridStyles) ?>">
|
||||||
<div class="dashboard-widget__head">
|
<div class="dashboard-widget__head">
|
||||||
@@ -224,8 +290,22 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (($itemType === 'iframe' || ($pageModule !== null && (string) ($pageModule['module_type'] ?? '') === 'iframe')) && $targetUrl !== ''): ?>
|
<?php if ($itemType === 'bookmark_group' && $bookmarks !== []): ?>
|
||||||
|
<div class="dashboard-links">
|
||||||
|
<?php foreach ($bookmarks as $bookmark): ?>
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="<?= e($bookmark['url']) ?>" target="_blank" rel="noreferrer"><?= e($bookmark['label']) ?></a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif (($itemType === 'iframe' || ($pageModule !== null && (string) ($pageModule['module_type'] ?? '') === 'iframe')) && $targetUrl !== ''): ?>
|
||||||
<iframe class="dashboard-widget__frame" src="<?= e($targetUrl) ?>" loading="lazy" referrerpolicy="no-referrer"></iframe>
|
<iframe class="dashboard-widget__frame" src="<?= e($targetUrl) ?>" loading="lazy" referrerpolicy="no-referrer"></iframe>
|
||||||
|
<?php elseif ($appEntry !== null): ?>
|
||||||
|
<div class="dashboard-widget__meta">
|
||||||
|
<?php if (!empty($appEntry['icon_url'])): ?>
|
||||||
|
<img class="dashboard-app-icon" src="<?= e((string) $appEntry['icon_url']) ?>" alt="">
|
||||||
|
<?php endif; ?>
|
||||||
|
<p><?= e((string) ($appEntry['description'] ?? $targetUrl)) ?></p>
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="<?= e($targetUrl) ?>" target="_blank" rel="noreferrer">App öffnen</a>
|
||||||
|
</div>
|
||||||
<?php elseif ($pageModule !== null): ?>
|
<?php elseif ($pageModule !== null): ?>
|
||||||
<div class="dashboard-widget__meta">
|
<div class="dashboard-widget__meta">
|
||||||
<p><?= e((string) ($pageModule['description'] ?? 'Seitenmodul aus der globalen Nexus-Verwaltung.')) ?></p>
|
<p><?= e((string) ($pageModule['description'] ?? 'Seitenmodul aus der globalen Nexus-Verwaltung.')) ?></p>
|
||||||
@@ -249,11 +329,17 @@ $GLOBALS['layout_header_text'] = 'Persönliche Arbeitsfläche mit frei platzierb
|
|||||||
const typeSelect = document.querySelector('[data-dashboard-item-type]');
|
const typeSelect = document.querySelector('[data-dashboard-item-type]');
|
||||||
const urlField = document.querySelector('[data-dashboard-target-url]');
|
const urlField = document.querySelector('[data-dashboard-target-url]');
|
||||||
const pageModuleField = document.querySelector('[data-dashboard-page-module]');
|
const pageModuleField = document.querySelector('[data-dashboard-page-module]');
|
||||||
if (!typeSelect || !urlField || !pageModuleField) return;
|
const bookmarksField = document.querySelector('[data-dashboard-bookmarks]');
|
||||||
|
const appField = document.querySelector('[data-dashboard-app]');
|
||||||
|
const widgetTemplateField = document.querySelector('[data-dashboard-widget-template]');
|
||||||
|
if (!typeSelect || !urlField || !pageModuleField || !bookmarksField || !appField || !widgetTemplateField) return;
|
||||||
const sync = () => {
|
const sync = () => {
|
||||||
const isPageModule = typeSelect.value === 'page_module';
|
const value = typeSelect.value;
|
||||||
urlField.hidden = isPageModule;
|
urlField.hidden = !['link', 'iframe'].includes(value);
|
||||||
pageModuleField.hidden = !isPageModule;
|
pageModuleField.hidden = value !== 'page_module';
|
||||||
|
bookmarksField.hidden = value !== 'bookmark_group';
|
||||||
|
appField.hidden = value !== 'app';
|
||||||
|
widgetTemplateField.hidden = value !== 'widget_template';
|
||||||
};
|
};
|
||||||
typeSelect.addEventListener('change', sync);
|
typeSelect.addEventListener('change', sync);
|
||||||
sync();
|
sync();
|
||||||
|
|||||||
@@ -3,202 +3,143 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
$auth = app()->auth();
|
$auth = app()->auth();
|
||||||
$authUser = $auth->user();
|
$authUser = $auth->user();
|
||||||
|
$service = dashboards();
|
||||||
$ownerKey = auth_user_key();
|
$ownerKey = auth_user_key();
|
||||||
$groups = auth_groups();
|
$groups = auth_groups();
|
||||||
$service = dashboards();
|
$selectedDashboardId = (int) ($_GET['board'] ?? 0);
|
||||||
$modules = array_values(array_filter(
|
$currentDashboard = $service->available()
|
||||||
$auth->filterModules(modules()->all()),
|
? $service->resolveHomeDashboard($ownerKey !== '' ? $ownerKey : null, $groups, $authUser !== null, $selectedDashboardId)
|
||||||
static fn (array $module): bool => !empty($module['enabled'])
|
: null;
|
||||||
));
|
$dashboardItems = $currentDashboard !== null ? $service->listItems((int) ($currentDashboard['id'] ?? 0)) : [];
|
||||||
|
$accessibleApps = ($authUser !== null && $service->available()) ? $service->listApps($ownerKey, true) : [];
|
||||||
$dashboardsList = [];
|
$appsById = [];
|
||||||
$pageModules = [];
|
foreach ($accessibleApps as $appEntry) {
|
||||||
$defaultDashboard = null;
|
$appsById[(int) ($appEntry['id'] ?? 0)] = $appEntry;
|
||||||
$dashboardItems = [];
|
|
||||||
|
|
||||||
if ($authUser !== null && $service->available() && $ownerKey !== '') {
|
|
||||||
$defaultDashboard = $service->ensureDefaultDashboard($ownerKey, 'Mein Dashboard');
|
|
||||||
$dashboardsList = $service->listAccessibleDashboards($ownerKey, $groups);
|
|
||||||
$pageModules = $service->listPageModulesForOwner($ownerKey);
|
|
||||||
if ($defaultDashboard !== []) {
|
|
||||||
$dashboardItems = $service->listItems((int) ($defaultDashboard['id'] ?? 0));
|
|
||||||
}
|
}
|
||||||
|
$widgetTemplates = ($authUser !== null && $service->available()) ? $service->listWidgetTemplates($ownerKey, true) : [];
|
||||||
|
$widgetTemplatesById = [];
|
||||||
|
foreach ($widgetTemplates as $template) {
|
||||||
|
$widgetTemplatesById[(int) ($template['id'] ?? 0)] = $template;
|
||||||
}
|
}
|
||||||
|
|
||||||
$GLOBALS['layout_header_base_title'] = 'Nexus';
|
$GLOBALS['layout_header_base_title'] = 'Nexus';
|
||||||
$GLOBALS['layout_header_title'] = 'Nexus';
|
$GLOBALS['layout_header_title'] = 'Nexus';
|
||||||
$GLOBALS['layout_header_context'] = 'Übersicht';
|
$GLOBALS['layout_header_context'] = $currentDashboard !== null ? (string) ($currentDashboard['title'] ?? 'Dashboard') : 'Dashboard';
|
||||||
$GLOBALS['layout_header_text'] = $authUser === null
|
$GLOBALS['layout_header_text'] = $currentDashboard !== null
|
||||||
? 'Zentraler Einstieg in Nexus und die verfügbaren Bereiche.'
|
? (string) ($currentDashboard['description'] ?? '')
|
||||||
: 'Persönliche Übersicht aus Dashboards, Seitenmodulen und klassischen Modulen.';
|
: 'Kein öffentliches oder persönliches Home-Dashboard verfügbar.';
|
||||||
|
|
||||||
|
$renderBookmarks = static function (array $config): array {
|
||||||
|
$items = [];
|
||||||
|
$raw = trim((string) ($config['bookmarks'] ?? ''));
|
||||||
|
if ($raw === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
foreach (preg_split('/\r\n|\r|\n/', $raw) ?: [] as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
if ($line === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$label, $url] = array_pad(array_map('trim', explode('|', $line, 2)), 2, '');
|
||||||
|
if ($label !== '' && $url !== '') {
|
||||||
|
$items[] = ['label' => $label, 'url' => $url];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $items;
|
||||||
|
};
|
||||||
?>
|
?>
|
||||||
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
||||||
|
<?php if ($currentDashboard === null): ?>
|
||||||
|
<section class="section-box">
|
||||||
|
<h2>Kein Home-Dashboard verfügbar</h2>
|
||||||
|
<p class="muted">Lege als Administrator ein öffentliches Dashboard fest oder richte dein persönliches Dashboard ein.</p>
|
||||||
<?php if ($authUser !== null): ?>
|
<?php if ($authUser !== null): ?>
|
||||||
<header class="module-hero submenu-box">
|
<a class="module-button module-button--secondary module-button--small" href="/dashboard">Zum Dashboard</a>
|
||||||
<div class="module-hero-top module-hero-top--compact">
|
|
||||||
<nav class="module-tabs" aria-label="Nexus Navigation">
|
|
||||||
<a class="module-button module-button--tab-active" href="/">Nexus Übersicht</a>
|
|
||||||
<a class="module-button module-button--tab" href="/dashboard">Dashboard</a>
|
|
||||||
<a class="module-button module-button--tab" href="/dashboards">Dashboards</a>
|
|
||||||
<a class="module-button module-button--tab" href="/integrations">Integrationen</a>
|
|
||||||
<a class="module-button module-button--tab" href="/page-modules">Seitenmodule</a>
|
|
||||||
<?php if (auth_is_admin()): ?>
|
|
||||||
<a class="module-button module-button--tab" href="/modules">Aktive Module</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</nav>
|
|
||||||
<div class="module-hero-actions module-submenu-actions">
|
|
||||||
<a class="module-button module-button--secondary module-button--small" href="/settings">Nexus Einstellungen</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section class="section-box">
|
|
||||||
<h2>Mein Standard-Dashboard</h2>
|
|
||||||
<p class="muted">Der schnellste Einstieg in deine wichtigsten Nexus-Inhalte.</p>
|
|
||||||
<?php if ($defaultDashboard === null || $defaultDashboard === []): ?>
|
|
||||||
<div class="dashboard-empty">Kein Dashboard verfügbar.</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="nexus-quick-grid">
|
|
||||||
<article class="card-box nexus-stat-card">
|
|
||||||
<span class="module-admin-meta__label">Dashboard</span>
|
|
||||||
<strong><?= e((string) ($defaultDashboard['title'] ?? 'Mein Dashboard')) ?></strong>
|
|
||||||
<p class="muted"><?= e((string) ($defaultDashboard['description'] ?? 'Persönliches Standard-Dashboard')) ?></p>
|
|
||||||
<a class="module-button module-button--secondary module-button--small" href="/dashboard?id=<?= (int) ($defaultDashboard['id'] ?? 0) ?>">Öffnen</a>
|
|
||||||
</article>
|
|
||||||
<article class="card-box nexus-stat-card">
|
|
||||||
<span class="module-admin-meta__label">Elemente</span>
|
|
||||||
<strong><?= count($dashboardItems) ?></strong>
|
|
||||||
<p class="muted">Aktive Dashboard-Elemente im Standard-Dashboard.</p>
|
|
||||||
</article>
|
|
||||||
<article class="card-box nexus-stat-card">
|
|
||||||
<span class="module-admin-meta__label">Seitenmodule</span>
|
|
||||||
<strong><?= count($pageModules) ?></strong>
|
|
||||||
<p class="muted">Eigene, on-the-fly angelegte Seitenmodule.</p>
|
|
||||||
</article>
|
|
||||||
<article class="card-box nexus-stat-card">
|
|
||||||
<span class="module-admin-meta__label">Klassische Module</span>
|
|
||||||
<strong><?= count($modules) ?></strong>
|
|
||||||
<p class="muted">Aktuell sichtbare, klassische Nexus-Module.</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
|
<?php elseif ($dashboardItems === []): ?>
|
||||||
<section class="section-box">
|
<section class="section-box">
|
||||||
<h2>Dashboards</h2>
|
<h2><?= e((string) ($currentDashboard['title'] ?? 'Dashboard')) ?></h2>
|
||||||
<p class="muted">Eigene und freigegebene Dashboards im globalen Nexus-System.</p>
|
<p class="muted">Dieses Dashboard enthält noch keine Elemente.</p>
|
||||||
<div class="module-admin-grid module-admin-grid--compact">
|
<?php if ($authUser !== null): ?>
|
||||||
<?php foreach (array_slice($dashboardsList, 0, 6) as $dashboard): ?>
|
<a class="module-button module-button--secondary module-button--small" href="/dashboard?id=<?= (int) ($currentDashboard['id'] ?? 0) ?>">Dashboard bearbeiten</a>
|
||||||
<article class="card-box module-admin-card">
|
<?php endif; ?>
|
||||||
<div class="module-admin-card__head">
|
</section>
|
||||||
<div class="module-admin-card__title">
|
<?php else: ?>
|
||||||
<h2><?= e((string) ($dashboard['title'] ?? 'Dashboard')) ?></h2>
|
<div class="dashboard-grid">
|
||||||
<p><?= e((string) ($dashboard['description'] ?? 'Flexible Dashboard-Fläche für Widgets und Seitenmodule.')) ?></p>
|
<?php foreach ($dashboardItems as $item): ?>
|
||||||
|
<?php
|
||||||
|
$itemType = (string) ($item['item_type'] ?? 'link');
|
||||||
|
$config = is_array($item['config'] ?? null) ? $item['config'] : [];
|
||||||
|
if ($itemType === 'widget_template' && !empty($config['widget_template_id']) && isset($widgetTemplatesById[(int) $config['widget_template_id']])) {
|
||||||
|
$template = $widgetTemplatesById[(int) $config['widget_template_id']];
|
||||||
|
$itemType = (string) ($template['widget_type'] ?? $itemType);
|
||||||
|
$config = array_merge(is_array($template['config'] ?? null) ? $template['config'] : [], $config);
|
||||||
|
}
|
||||||
|
$columnSpan = max(1, min(4, (int) ($item['column_span'] ?? 1)));
|
||||||
|
$rowSpan = max(1, min(4, (int) ($item['row_span'] ?? 1)));
|
||||||
|
$gridStyles = 'grid-column: span ' . $columnSpan . '; grid-row: span ' . $rowSpan . ';';
|
||||||
|
if (!empty($item['grid_column'])) {
|
||||||
|
$gridStyles .= 'grid-column-start:' . (int) $item['grid_column'] . ';';
|
||||||
|
}
|
||||||
|
if (!empty($item['grid_row'])) {
|
||||||
|
$gridStyles .= 'grid-row-start:' . (int) $item['grid_row'] . ';';
|
||||||
|
}
|
||||||
|
$targetUrl = trim((string) ($config['url'] ?? ''));
|
||||||
|
$pageModule = null;
|
||||||
|
if ($itemType === 'page_module' && !empty($config['page_module_id'])) {
|
||||||
|
$pageModule = $service->getPageModule((int) $config['page_module_id'], $ownerKey, $groups);
|
||||||
|
$targetUrl = trim((string) ($pageModule['target_url'] ?? $targetUrl));
|
||||||
|
}
|
||||||
|
$appEntry = null;
|
||||||
|
if ($itemType === 'app' && !empty($config['app_id'])) {
|
||||||
|
$appEntry = $appsById[(int) $config['app_id']] ?? null;
|
||||||
|
$targetUrl = trim((string) ($appEntry['app_url'] ?? $targetUrl));
|
||||||
|
}
|
||||||
|
$bookmarks = $itemType === 'bookmark_group' ? $renderBookmarks($config) : [];
|
||||||
|
?>
|
||||||
|
<article class="card-box dashboard-widget" style="<?= e($gridStyles) ?>">
|
||||||
|
<div class="dashboard-widget__head">
|
||||||
|
<div>
|
||||||
|
<span class="module-admin-meta__label"><?= e(strtoupper(str_replace('_', ' ', $itemType))) ?></span>
|
||||||
|
<h2><?= e((string) ($item['title'] ?? 'Element')) ?></h2>
|
||||||
|
<?php if (!empty($item['description'])): ?>
|
||||||
|
<p><?= e((string) $item['description']) ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="module-admin-meta">
|
|
||||||
<div class="module-admin-meta__item">
|
<?php if ($itemType === 'bookmark_group' && $bookmarks !== []): ?>
|
||||||
<span class="module-admin-meta__label">Sichtbarkeit</span>
|
<div class="dashboard-links">
|
||||||
<strong class="module-admin-badge"><?= e(ucfirst((string) ($dashboard['visibility'] ?? 'private'))) ?></strong>
|
<?php foreach ($bookmarks as $bookmark): ?>
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="<?= e($bookmark['url']) ?>" target="_blank" rel="noreferrer"><?= e($bookmark['label']) ?></a>
|
||||||
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<?php elseif (($itemType === 'iframe' || ($pageModule !== null && (string) ($pageModule['module_type'] ?? '') === 'iframe')) && $targetUrl !== ''): ?>
|
||||||
|
<iframe class="dashboard-widget__frame" src="<?= e($targetUrl) ?>" loading="lazy" referrerpolicy="no-referrer"></iframe>
|
||||||
|
<?php elseif ($appEntry !== null): ?>
|
||||||
|
<div class="dashboard-widget__meta">
|
||||||
|
<?php if (!empty($appEntry['icon_url'])): ?>
|
||||||
|
<img class="dashboard-app-icon" src="<?= e((string) $appEntry['icon_url']) ?>" alt="">
|
||||||
|
<?php endif; ?>
|
||||||
|
<p><?= e((string) ($appEntry['description'] ?? $targetUrl)) ?></p>
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="<?= e($targetUrl) ?>" target="_blank" rel="noreferrer">App öffnen</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="module-admin-actions">
|
<?php elseif ($pageModule !== null): ?>
|
||||||
<a class="module-button module-button--secondary module-button--small" href="/dashboard?id=<?= (int) ($dashboard['id'] ?? 0) ?>">Öffnen</a>
|
<div class="dashboard-widget__meta">
|
||||||
|
<p><?= e((string) ($pageModule['description'] ?? 'Seitenmodul aus dem Nexus-System.')) ?></p>
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="/page-modules/view/<?= (int) ($pageModule['id'] ?? 0) ?>">Öffnen</a>
|
||||||
</div>
|
</div>
|
||||||
|
<?php elseif ($targetUrl !== ''): ?>
|
||||||
|
<div class="dashboard-widget__meta">
|
||||||
|
<p><?= e($targetUrl) ?></p>
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="<?= e($targetUrl) ?>" target="_blank" rel="noreferrer">Öffnen</a>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="dashboard-empty">Für dieses Element ist noch kein Inhalt hinterlegt.</div>
|
||||||
|
<?php endif; ?>
|
||||||
</article>
|
</article>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="module-admin-grid">
|
|
||||||
<article class="card-box module-admin-card">
|
|
||||||
<div class="module-admin-card__head">
|
|
||||||
<div class="module-admin-card__title">
|
|
||||||
<h2>Seitenmodule</h2>
|
|
||||||
<p>On-the-fly angelegte Zielseiten und eingebettete Tools.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="module-admin-meta">
|
|
||||||
<div class="module-admin-meta__item">
|
|
||||||
<span class="module-admin-meta__label">Anzahl</span>
|
|
||||||
<strong class="module-admin-badge module-admin-badge--success"><?= count($pageModules) ?></strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="module-admin-actions">
|
|
||||||
<a class="module-button module-button--secondary module-button--small" href="/page-modules">Verwalten</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="card-box module-admin-card">
|
|
||||||
<div class="module-admin-card__head">
|
|
||||||
<div class="module-admin-card__title">
|
|
||||||
<h2>Integrationen</h2>
|
|
||||||
<p>Zentrale Anbindungen an Home Assistant, Pi-hole, Proxmox und andere Systeme.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="module-admin-actions">
|
|
||||||
<a class="module-button module-button--secondary module-button--small" href="/integrations">Verwalten</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="card-box module-admin-card">
|
|
||||||
<div class="module-admin-card__head">
|
|
||||||
<div class="module-admin-card__title">
|
|
||||||
<h2>Dashboards konfigurieren</h2>
|
|
||||||
<p>Eigene Dashboard-Flächen, Reihenfolge und Standard-Dashboard verwalten.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="module-admin-actions">
|
|
||||||
<a class="module-button module-button--secondary module-button--small" href="/dashboards">Öffnen</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="section-box">
|
|
||||||
<h2>Klassische Module</h2>
|
|
||||||
<p class="muted">Die bisherigen Nexus-Module bleiben parallel zum neuen Grundgerüst bestehen.</p>
|
|
||||||
<div class="module-list">
|
|
||||||
<?php foreach ($modules as $module): ?>
|
|
||||||
<a class="module-row" href="<?= e((string) ($module['entry'] ?? ('/module/' . $module['name']))) ?>">
|
|
||||||
<span class="module-row__icon"><?= e(strtoupper(substr((string) ($module['title'] ?? $module['name']), 0, 1))) ?></span>
|
|
||||||
<span class="module-row__content">
|
|
||||||
<strong class="module-title"><?= e((string) ($module['title'] ?? $module['name'] ?? 'Modul')) ?></strong>
|
|
||||||
<span class="module-desc"><?= e((string) ($module['description'] ?? '')) ?></span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<?php else: ?>
|
|
||||||
<section class="section-box">
|
|
||||||
<h2>Nexus Einstieg</h2>
|
|
||||||
<p class="muted">Nach der Anmeldung stehen persönliche Dashboards, Integrationen und Seitenmodule bereit.</p>
|
|
||||||
<div class="module-admin-grid">
|
|
||||||
<article class="card-box module-admin-card">
|
|
||||||
<div class="module-admin-card__head">
|
|
||||||
<div class="module-admin-card__title">
|
|
||||||
<h2>Persönliche Dashboards</h2>
|
|
||||||
<p>Mehrere frei konfigurierbare Übersichten pro Benutzer.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article class="card-box module-admin-card">
|
|
||||||
<div class="module-admin-card__head">
|
|
||||||
<div class="module-admin-card__title">
|
|
||||||
<h2>Integrationen</h2>
|
|
||||||
<p>Zentrale Anbindungen für Fremdsysteme und externe Datenquellen.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article class="card-box module-admin-card">
|
|
||||||
<div class="module-admin-card__head">
|
|
||||||
<div class="module-admin-card__title">
|
|
||||||
<h2>Seitenmodule</h2>
|
|
||||||
<p>On-the-fly angelegte Links und eingebettete Weboberflächen.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div></div></div>
|
</div></div></div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
require_auth();
|
require_admin();
|
||||||
|
|
||||||
$service = dashboards();
|
$service = dashboards();
|
||||||
$ownerKey = auth_user_key();
|
$ownerKey = 'system';
|
||||||
$notice = null;
|
$notice = null;
|
||||||
$error = null;
|
$error = null;
|
||||||
|
|
||||||
@@ -39,19 +39,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
|
|
||||||
$integrations = $service->listIntegrationsForOwner($ownerKey);
|
$integrations = $service->listIntegrationsForOwner($ownerKey);
|
||||||
|
|
||||||
$GLOBALS['layout_header_base_title'] = 'Nexus';
|
$GLOBALS['layout_header_base_title'] = 'Nexus Setup';
|
||||||
$GLOBALS['layout_header_title'] = 'Nexus';
|
$GLOBALS['layout_header_title'] = 'Nexus Setup';
|
||||||
$GLOBALS['layout_header_context'] = 'Integrationen';
|
$GLOBALS['layout_header_context'] = 'Integrationen';
|
||||||
$GLOBALS['layout_header_text'] = 'Zentrale Anbindungen an externe Systeme, getrennt vom klassischen Modulsystem.';
|
$GLOBALS['layout_header_text'] = 'Globale Integrationen für spätere Widgets, Apps und Suchfunktionen.';
|
||||||
?>
|
?>
|
||||||
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
||||||
<header class="module-hero submenu-box">
|
<header class="module-hero submenu-box">
|
||||||
<div class="module-hero-top module-hero-top--compact">
|
<div class="module-hero-top module-hero-top--compact">
|
||||||
<nav class="module-tabs" aria-label="Integration Navigation">
|
<nav class="module-tabs" aria-label="Nexus Setup Navigation">
|
||||||
<a class="module-button module-button--tab" href="/dashboard">Dashboard</a>
|
<a class="module-button module-button--tab" href="/settings">Allgemein</a>
|
||||||
<a class="module-button module-button--tab" href="/dashboards">Dashboards</a>
|
<a class="module-button module-button--tab" href="/settings/widgets">Widgets</a>
|
||||||
<a class="module-button module-button--tab-active" href="/integrations">Integrationen</a>
|
<a class="module-button module-button--tab-active" href="/integrations">Integrationen</a>
|
||||||
<a class="module-button module-button--tab" href="/page-modules">Seitenmodule</a>
|
<a class="module-button module-button--tab" href="/settings/search-engines">Suchmaschinen</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/apps">Apps</a>
|
||||||
|
<a class="module-button module-button--tab" href="/dashboards">Dashboards</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="module-hero-actions module-submenu-actions">
|
<div class="module-hero-actions module-submenu-actions">
|
||||||
<a class="module-button module-button--secondary module-button--small" href="/">Nexus Übersicht</a>
|
<a class="module-button module-button--secondary module-button--small" href="/">Nexus Übersicht</a>
|
||||||
@@ -93,7 +95,6 @@ $GLOBALS['layout_header_text'] = 'Zentrale Anbindungen an externe Systeme, getre
|
|||||||
<label class="setup-field muted">
|
<label class="setup-field muted">
|
||||||
<span>Sichtbarkeit</span>
|
<span>Sichtbarkeit</span>
|
||||||
<select name="visibility">
|
<select name="visibility">
|
||||||
<option value="private">Privat</option>
|
|
||||||
<option value="public">Öffentlich</option>
|
<option value="public">Öffentlich</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ $timezoneOptions = modules()->timezones();
|
|||||||
$nexusSettings = nexus_settings();
|
$nexusSettings = nexus_settings();
|
||||||
$isAdmin = auth_is_admin();
|
$isAdmin = auth_is_admin();
|
||||||
$notice = null;
|
$notice = null;
|
||||||
|
$publicDashboards = $isAdmin ? dashboards()->listPublicDashboards() : [];
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$settingsSection = trim((string) ($_POST['settings_section'] ?? 'theme'));
|
$settingsSection = trim((string) ($_POST['settings_section'] ?? 'theme'));
|
||||||
@@ -19,6 +20,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$displayTimezoneCustom = isset($_POST['display_timezone_custom']) ? '1' : '0';
|
$displayTimezoneCustom = isset($_POST['display_timezone_custom']) ? '1' : '0';
|
||||||
$displayTimezone = trim((string) ($_POST['display_timezone'] ?? ''));
|
$displayTimezone = trim((string) ($_POST['display_timezone'] ?? ''));
|
||||||
$cronTimezone = trim((string) ($_POST['cron_timezone'] ?? ''));
|
$cronTimezone = trim((string) ($_POST['cron_timezone'] ?? ''));
|
||||||
|
$globalHomeDashboardId = (int) ($_POST['global_home_dashboard_id'] ?? 0);
|
||||||
|
|
||||||
foreach (['displayTimezone' => $displayTimezone, 'cronTimezone' => $cronTimezone] as $key => $value) {
|
foreach (['displayTimezone' => $displayTimezone, 'cronTimezone' => $cronTimezone] as $key => $value) {
|
||||||
if ($value !== '') {
|
if ($value !== '') {
|
||||||
@@ -33,6 +35,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$nexusSettings['display_timezone_custom'] = $displayTimezoneCustom;
|
$nexusSettings['display_timezone_custom'] = $displayTimezoneCustom;
|
||||||
$nexusSettings['display_timezone'] = $displayTimezoneCustom === '1' ? $displayTimezone : '';
|
$nexusSettings['display_timezone'] = $displayTimezoneCustom === '1' ? $displayTimezone : '';
|
||||||
$nexusSettings['cron_timezone'] = $cronTimezone;
|
$nexusSettings['cron_timezone'] = $cronTimezone;
|
||||||
|
$nexusSettings['global_home_dashboard_id'] = $globalHomeDashboardId > 0 ? (string) $globalHomeDashboardId : '';
|
||||||
nexus_save_settings($nexusSettings);
|
nexus_save_settings($nexusSettings);
|
||||||
$nexusSettings = nexus_settings();
|
$nexusSettings = nexus_settings();
|
||||||
$notice = 'Nexus-Einstellungen gespeichert.';
|
$notice = 'Nexus-Einstellungen gespeichert.';
|
||||||
@@ -53,11 +56,35 @@ $effectiveCronTimezone = nexus_cron_timezone_name();
|
|||||||
$displayTimezoneCustom = !empty($nexusSettings['display_timezone_custom']);
|
$displayTimezoneCustom = !empty($nexusSettings['display_timezone_custom']);
|
||||||
$savedDisplayTimezone = trim((string) ($nexusSettings['display_timezone'] ?? ''));
|
$savedDisplayTimezone = trim((string) ($nexusSettings['display_timezone'] ?? ''));
|
||||||
$savedCronTimezone = trim((string) ($nexusSettings['cron_timezone'] ?? ''));
|
$savedCronTimezone = trim((string) ($nexusSettings['cron_timezone'] ?? ''));
|
||||||
|
$globalHomeDashboardId = (int) ($nexusSettings['global_home_dashboard_id'] ?? 0);
|
||||||
|
|
||||||
|
$GLOBALS['layout_header_base_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_context'] = 'Allgemein';
|
||||||
|
$GLOBALS['layout_header_text'] = 'Globale Grundeinstellungen, Home-Dashboard und systemweite Standardwerte.';
|
||||||
?>
|
?>
|
||||||
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
||||||
|
<header class="module-hero submenu-box">
|
||||||
|
<div class="module-hero-top module-hero-top--compact">
|
||||||
|
<nav class="module-tabs" aria-label="Nexus Setup Navigation">
|
||||||
|
<a class="module-button module-button--tab-active" href="/settings">Allgemein</a>
|
||||||
|
<?php if ($isAdmin): ?>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/widgets">Widgets</a>
|
||||||
|
<a class="module-button module-button--tab" href="/integrations">Integrationen</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/search-engines">Suchmaschinen</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/apps">Apps</a>
|
||||||
|
<a class="module-button module-button--tab" href="/dashboards">Dashboards</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</nav>
|
||||||
|
<div class="module-hero-actions module-submenu-actions">
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="/">Nexus Übersicht</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<section class="section-box">
|
<section class="section-box">
|
||||||
<h1>Nexus Einstellungen</h1>
|
<h1>Nexus Einstellungen</h1>
|
||||||
<p class="muted">Persönliche Anzeige und systemweite Standardwerte.</p>
|
<p class="muted">Persönliche Anzeige, Home-Dashboard und systemweite Standardwerte.</p>
|
||||||
|
|
||||||
<?php if ($notice): ?>
|
<?php if ($notice): ?>
|
||||||
<div class="section-box" style="margin-top:1rem; border-color:var(--accent-2);">
|
<div class="section-box" style="margin-top:1rem; border-color:var(--accent-2);">
|
||||||
@@ -115,6 +142,18 @@ $savedCronTimezone = trim((string) ($nexusSettings['cron_timezone'] ?? ''));
|
|||||||
<div><?= e($systemTimezone) ?></div>
|
<div><?= e($systemTimezone) ?></div>
|
||||||
<small class="muted">Diese Zeitzone wird genutzt, wenn keine globale Anzeige-Zeitzone gesetzt ist.</small>
|
<small class="muted">Diese Zeitzone wird genutzt, wenn keine globale Anzeige-Zeitzone gesetzt ist.</small>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Öffentliches Home-Dashboard</span>
|
||||||
|
<select name="global_home_dashboard_id">
|
||||||
|
<option value="0">Kein öffentliches Home-Dashboard</option>
|
||||||
|
<?php foreach ($publicDashboards as $dashboard): ?>
|
||||||
|
<option value="<?= (int) ($dashboard['id'] ?? 0) ?>" <?= (int) ($dashboard['id'] ?? 0) === $globalHomeDashboardId ? 'selected' : '' ?>>
|
||||||
|
<?= e((string) ($dashboard['title'] ?? 'Dashboard')) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<small class="muted">Dieses öffentliche Dashboard wird am Root-Pfad angezeigt, wenn Nutzer nicht eingeloggt sind.</small>
|
||||||
|
</label>
|
||||||
<div class="setup-field muted">
|
<div class="setup-field muted">
|
||||||
<span>Anzeige-Zeitzone</span>
|
<span>Anzeige-Zeitzone</span>
|
||||||
<label style="display:flex; align-items:center; gap:10px;">
|
<label style="display:flex; align-items:center; gap:10px;">
|
||||||
|
|||||||
125
partials/landingpages/users/settings_apps.php
Normal file
125
partials/landingpages/users/settings_apps.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_admin();
|
||||||
|
|
||||||
|
$service = dashboards();
|
||||||
|
$ownerKey = 'system';
|
||||||
|
$notice = null;
|
||||||
|
$error = null;
|
||||||
|
$integrations = $service->listIntegrationsForOwner($ownerKey);
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = trim((string) ($_POST['action'] ?? ''));
|
||||||
|
try {
|
||||||
|
if ($action === 'create_app') {
|
||||||
|
$service->createApp($ownerKey, [
|
||||||
|
'name' => trim((string) ($_POST['name'] ?? '')),
|
||||||
|
'description' => trim((string) ($_POST['description'] ?? '')),
|
||||||
|
'app_url' => trim((string) ($_POST['app_url'] ?? '')),
|
||||||
|
'icon_url' => trim((string) ($_POST['icon_url'] ?? '')),
|
||||||
|
'integration_id' => (int) ($_POST['integration_id'] ?? 0),
|
||||||
|
'visibility' => 'public',
|
||||||
|
]);
|
||||||
|
$notice = 'App gespeichert.';
|
||||||
|
} elseif ($action === 'delete_app') {
|
||||||
|
$service->deleteApp((int) ($_POST['app_id'] ?? 0), $ownerKey);
|
||||||
|
$notice = 'App gelöscht.';
|
||||||
|
}
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$error = $exception->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$apps = $service->listApps($ownerKey, true);
|
||||||
|
|
||||||
|
$GLOBALS['layout_header_base_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_context'] = 'Apps';
|
||||||
|
$GLOBALS['layout_header_text'] = 'Globale Apps, die Nutzer später ihren Dashboards hinzufügen können.';
|
||||||
|
?>
|
||||||
|
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
||||||
|
<header class="module-hero submenu-box">
|
||||||
|
<div class="module-hero-top module-hero-top--compact">
|
||||||
|
<nav class="module-tabs" aria-label="Nexus Setup Navigation">
|
||||||
|
<a class="module-button module-button--tab" href="/settings">Allgemein</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/widgets">Widgets</a>
|
||||||
|
<a class="module-button module-button--tab" href="/integrations">Integrationen</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/search-engines">Suchmaschinen</a>
|
||||||
|
<a class="module-button module-button--tab-active" href="/settings/apps">Apps</a>
|
||||||
|
<a class="module-button module-button--tab" href="/dashboards">Dashboards</a>
|
||||||
|
</nav>
|
||||||
|
<div class="module-hero-actions module-submenu-actions">
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="/">Nexus Übersicht</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<?php if ($error !== null): ?>
|
||||||
|
<section class="section-box"><?= e($error) ?></section>
|
||||||
|
<?php elseif ($notice !== null): ?>
|
||||||
|
<section class="section-box"><?= e($notice) ?></section>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<section class="section-box">
|
||||||
|
<h2>Neue App</h2>
|
||||||
|
<form method="post" class="setup-form">
|
||||||
|
<input type="hidden" name="action" value="create_app">
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Name</span>
|
||||||
|
<input type="text" name="name" required>
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>App-URL</span>
|
||||||
|
<input type="url" name="app_url" required placeholder="https://...">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Beschreibung</span>
|
||||||
|
<input type="text" name="description">
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Icon-URL optional</span>
|
||||||
|
<input type="url" name="icon_url" placeholder="https://.../icon.png">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Integration optional</span>
|
||||||
|
<select name="integration_id">
|
||||||
|
<option value="0">Keine Integration</option>
|
||||||
|
<?php foreach ($integrations as $integration): ?>
|
||||||
|
<option value="<?= (int) ($integration['id'] ?? 0) ?>"><?= e((string) ($integration['name'] ?? 'Integration')) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-actions setup-actions--footer">
|
||||||
|
<button class="cta-button" type="submit">App speichern</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="module-admin-grid">
|
||||||
|
<?php foreach ($apps as $appEntry): ?>
|
||||||
|
<article class="card-box module-admin-card">
|
||||||
|
<div class="module-admin-card__head">
|
||||||
|
<div class="module-admin-card__title">
|
||||||
|
<h2><?= e((string) ($appEntry['name'] ?? 'App')) ?></h2>
|
||||||
|
<p><?= e((string) ($appEntry['description'] ?? ($appEntry['app_url'] ?? ''))) ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-admin-actions">
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="<?= e((string) ($appEntry['app_url'] ?? '#')) ?>" target="_blank" rel="noreferrer">Öffnen</a>
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="action" value="delete_app">
|
||||||
|
<input type="hidden" name="app_id" value="<?= (int) ($appEntry['id'] ?? 0) ?>">
|
||||||
|
<button class="module-button module-button--secondary module-button--small" type="submit">Löschen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div></div></div>
|
||||||
142
partials/landingpages/users/settings_search_engines.php
Normal file
142
partials/landingpages/users/settings_search_engines.php
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_admin();
|
||||||
|
|
||||||
|
$service = dashboards();
|
||||||
|
$ownerKey = 'system';
|
||||||
|
$notice = null;
|
||||||
|
$error = null;
|
||||||
|
$integrations = $service->listIntegrationsForOwner($ownerKey);
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = trim((string) ($_POST['action'] ?? ''));
|
||||||
|
try {
|
||||||
|
if ($action === 'create_search_engine') {
|
||||||
|
$service->createSearchEngine($ownerKey, [
|
||||||
|
'name' => trim((string) ($_POST['name'] ?? '')),
|
||||||
|
'short_code' => trim((string) ($_POST['short_code'] ?? '')),
|
||||||
|
'engine_type' => trim((string) ($_POST['engine_type'] ?? 'template')),
|
||||||
|
'template_url' => trim((string) ($_POST['template_url'] ?? '')),
|
||||||
|
'integration_id' => (int) ($_POST['integration_id'] ?? 0),
|
||||||
|
'visibility' => 'public',
|
||||||
|
'is_default' => isset($_POST['is_default']),
|
||||||
|
]);
|
||||||
|
$notice = 'Suchmaschine gespeichert.';
|
||||||
|
} elseif ($action === 'delete_search_engine') {
|
||||||
|
$service->deleteSearchEngine((int) ($_POST['engine_id'] ?? 0), $ownerKey);
|
||||||
|
$notice = 'Suchmaschine gelöscht.';
|
||||||
|
}
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$error = $exception->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$engines = $service->listSearchEngines($ownerKey, true);
|
||||||
|
|
||||||
|
$GLOBALS['layout_header_base_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_context'] = 'Suchmaschinen';
|
||||||
|
$GLOBALS['layout_header_text'] = 'Globale Suchmaschinen für die spätere Suche im Nexus-System.';
|
||||||
|
?>
|
||||||
|
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
||||||
|
<header class="module-hero submenu-box">
|
||||||
|
<div class="module-hero-top module-hero-top--compact">
|
||||||
|
<nav class="module-tabs" aria-label="Nexus Setup Navigation">
|
||||||
|
<a class="module-button module-button--tab" href="/settings">Allgemein</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/widgets">Widgets</a>
|
||||||
|
<a class="module-button module-button--tab" href="/integrations">Integrationen</a>
|
||||||
|
<a class="module-button module-button--tab-active" href="/settings/search-engines">Suchmaschinen</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/apps">Apps</a>
|
||||||
|
<a class="module-button module-button--tab" href="/dashboards">Dashboards</a>
|
||||||
|
</nav>
|
||||||
|
<div class="module-hero-actions module-submenu-actions">
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="/">Nexus Übersicht</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<?php if ($error !== null): ?>
|
||||||
|
<section class="section-box"><?= e($error) ?></section>
|
||||||
|
<?php elseif ($notice !== null): ?>
|
||||||
|
<section class="section-box"><?= e($notice) ?></section>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<section class="section-box">
|
||||||
|
<h2>Neue Suchmaschine</h2>
|
||||||
|
<form method="post" class="setup-form">
|
||||||
|
<input type="hidden" name="action" value="create_search_engine">
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Name</span>
|
||||||
|
<input type="text" name="name" required>
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Kurzcode</span>
|
||||||
|
<input type="text" name="short_code" placeholder="g" required>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Typ</span>
|
||||||
|
<select name="engine_type">
|
||||||
|
<option value="template">Such-Template</option>
|
||||||
|
<option value="integration">Integration</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Such-URL</span>
|
||||||
|
<input type="url" name="template_url" placeholder="https://www.google.com/search?q=%s">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Integration optional</span>
|
||||||
|
<select name="integration_id">
|
||||||
|
<option value="0">Keine Integration</option>
|
||||||
|
<?php foreach ($integrations as $integration): ?>
|
||||||
|
<option value="<?= (int) ($integration['id'] ?? 0) ?>"><?= e((string) ($integration['name'] ?? 'Integration')) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<input type="checkbox" name="is_default" value="1">
|
||||||
|
<span>Als globale Standard-Suche setzen</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-actions setup-actions--footer">
|
||||||
|
<button class="cta-button" type="submit">Suchmaschine speichern</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="module-admin-grid">
|
||||||
|
<?php foreach ($engines as $engine): ?>
|
||||||
|
<article class="card-box module-admin-card">
|
||||||
|
<div class="module-admin-card__head">
|
||||||
|
<div class="module-admin-card__title">
|
||||||
|
<h2><?= e((string) ($engine['name'] ?? 'Suche')) ?></h2>
|
||||||
|
<p><?= e((string) (($engine['template_url'] ?? '') ?: 'Integration-basierte Suche.')) ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-admin-meta">
|
||||||
|
<div class="module-admin-meta__item">
|
||||||
|
<span class="module-admin-meta__label">Kurzcode</span>
|
||||||
|
<strong class="module-admin-badge"><?= e((string) ($engine['short_code'] ?? '')) ?></strong>
|
||||||
|
</div>
|
||||||
|
<div class="module-admin-meta__item">
|
||||||
|
<span class="module-admin-meta__label">Standard</span>
|
||||||
|
<strong class="module-admin-badge<?= !empty($engine['is_default']) ? ' module-admin-badge--success' : '' ?>"><?= !empty($engine['is_default']) ? 'Ja' : 'Nein' ?></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-admin-actions">
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="action" value="delete_search_engine">
|
||||||
|
<input type="hidden" name="engine_id" value="<?= (int) ($engine['id'] ?? 0) ?>">
|
||||||
|
<button class="module-button module-button--secondary module-button--small" type="submit">Löschen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div></div></div>
|
||||||
130
partials/landingpages/users/settings_widgets.php
Normal file
130
partials/landingpages/users/settings_widgets.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_admin();
|
||||||
|
|
||||||
|
$service = dashboards();
|
||||||
|
$ownerKey = 'system';
|
||||||
|
$notice = null;
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = trim((string) ($_POST['action'] ?? ''));
|
||||||
|
try {
|
||||||
|
if ($action === 'create_widget') {
|
||||||
|
$service->createWidgetTemplate($ownerKey, [
|
||||||
|
'name' => trim((string) ($_POST['name'] ?? '')),
|
||||||
|
'widget_type' => trim((string) ($_POST['widget_type'] ?? 'link')),
|
||||||
|
'description' => trim((string) ($_POST['description'] ?? '')),
|
||||||
|
'visibility' => 'public',
|
||||||
|
'config' => [
|
||||||
|
'url' => trim((string) ($_POST['target_url'] ?? '')),
|
||||||
|
'bookmarks' => trim((string) ($_POST['bookmarks'] ?? '')),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$notice = 'Widget-Vorlage gespeichert.';
|
||||||
|
} elseif ($action === 'delete_widget') {
|
||||||
|
$service->deleteWidgetTemplate((int) ($_POST['widget_id'] ?? 0), $ownerKey);
|
||||||
|
$notice = 'Widget-Vorlage gelöscht.';
|
||||||
|
}
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$error = $exception->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$widgets = $service->listWidgetTemplates($ownerKey, true);
|
||||||
|
|
||||||
|
$GLOBALS['layout_header_base_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_title'] = 'Nexus Setup';
|
||||||
|
$GLOBALS['layout_header_context'] = 'Widgets';
|
||||||
|
$GLOBALS['layout_header_text'] = 'Globale Widget-Vorlagen, die andere Nutzer in ihre Dashboards übernehmen können.';
|
||||||
|
?>
|
||||||
|
<div class="module-shell"><div class="module-page-bg"><div class="module-page-stack">
|
||||||
|
<header class="module-hero submenu-box">
|
||||||
|
<div class="module-hero-top module-hero-top--compact">
|
||||||
|
<nav class="module-tabs" aria-label="Nexus Setup Navigation">
|
||||||
|
<a class="module-button module-button--tab" href="/settings">Allgemein</a>
|
||||||
|
<a class="module-button module-button--tab-active" href="/settings/widgets">Widgets</a>
|
||||||
|
<a class="module-button module-button--tab" href="/integrations">Integrationen</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/search-engines">Suchmaschinen</a>
|
||||||
|
<a class="module-button module-button--tab" href="/settings/apps">Apps</a>
|
||||||
|
<a class="module-button module-button--tab" href="/dashboards">Dashboards</a>
|
||||||
|
</nav>
|
||||||
|
<div class="module-hero-actions module-submenu-actions">
|
||||||
|
<a class="module-button module-button--secondary module-button--small" href="/">Nexus Übersicht</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<?php if ($error !== null): ?>
|
||||||
|
<section class="section-box"><?= e($error) ?></section>
|
||||||
|
<?php elseif ($notice !== null): ?>
|
||||||
|
<section class="section-box"><?= e($notice) ?></section>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<section class="section-box">
|
||||||
|
<h2>Neue Widget-Vorlage</h2>
|
||||||
|
<form method="post" class="setup-form">
|
||||||
|
<input type="hidden" name="action" value="create_widget">
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Name</span>
|
||||||
|
<input type="text" name="name" required>
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Typ</span>
|
||||||
|
<select name="widget_type">
|
||||||
|
<option value="link">Link</option>
|
||||||
|
<option value="iframe">iFrame</option>
|
||||||
|
<option value="bookmark_group">Linkliste</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Beschreibung</span>
|
||||||
|
<input type="text" name="description">
|
||||||
|
</label>
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Ziel-URL</span>
|
||||||
|
<input type="url" name="target_url" placeholder="https://...">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Linkliste optional</span>
|
||||||
|
<textarea name="bookmarks" rows="5" placeholder="Name | https://ziel.example Monitoring | https://grafana.example"></textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setup-actions setup-actions--footer">
|
||||||
|
<button class="cta-button" type="submit">Widget speichern</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="module-admin-grid">
|
||||||
|
<?php foreach ($widgets as $widget): ?>
|
||||||
|
<article class="card-box module-admin-card">
|
||||||
|
<div class="module-admin-card__head">
|
||||||
|
<div class="module-admin-card__title">
|
||||||
|
<h2><?= e((string) ($widget['name'] ?? 'Widget')) ?></h2>
|
||||||
|
<p><?= e((string) ($widget['description'] ?? 'Globale Widget-Vorlage.')) ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-admin-meta">
|
||||||
|
<div class="module-admin-meta__item">
|
||||||
|
<span class="module-admin-meta__label">Typ</span>
|
||||||
|
<strong class="module-admin-badge"><?= e((string) ($widget['widget_type'] ?? 'link')) ?></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-admin-actions">
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="action" value="delete_widget">
|
||||||
|
<input type="hidden" name="widget_id" value="<?= (int) ($widget['id'] ?? 0) ?>">
|
||||||
|
<button class="module-button module-button--secondary module-button--small" type="submit">Löschen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div></div></div>
|
||||||
@@ -831,12 +831,26 @@ body.has-modal-open {
|
|||||||
align-content: start;
|
align-content: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-widget__meta p {
|
.dashboard-widget__meta p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text-soft);
|
color: var(--text-soft);
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-app-icon {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--border-soft) 85%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-widget__frame {
|
.dashboard-widget__frame {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ $publicPaths = [
|
|||||||
'auth/me',
|
'auth/me',
|
||||||
'module/pi_control/terminal_info',
|
'module/pi_control/terminal_info',
|
||||||
];
|
];
|
||||||
$requiresGlobalAuth = in_array($uriPath, ['settings', 'users', 'modules', 'modules/install', 'modules/sql-import', 'debug', 'exports/database.sql', 'dashboard', 'dashboards', 'integrations', 'page-modules'], true)
|
$requiresGlobalAuth = in_array($uriPath, ['settings', 'settings/widgets', 'settings/search-engines', 'settings/apps', 'users', 'modules', 'modules/install', 'modules/sql-import', 'debug', 'exports/database.sql', 'dashboard', 'dashboards', 'integrations', 'page-modules'], true)
|
||||||
|| str_starts_with($uriPath, 'modules/setup/')
|
|| str_starts_with($uriPath, 'modules/setup/')
|
||||||
|| str_starts_with($uriPath, 'modules/access/');
|
|| str_starts_with($uriPath, 'modules/access/');
|
||||||
if (str_starts_with($uriPath, 'page-modules/view/')) {
|
if (str_starts_with($uriPath, 'page-modules/view/')) {
|
||||||
@@ -275,6 +275,12 @@ if (str_starts_with($uriPath, 'modules/install')) {
|
|||||||
$target = $pagesBase . '/auth/logout.php';
|
$target = $pagesBase . '/auth/logout.php';
|
||||||
} elseif ($uriPath === 'settings') {
|
} elseif ($uriPath === 'settings') {
|
||||||
$target = $pagesBase . '/users/settings.php';
|
$target = $pagesBase . '/users/settings.php';
|
||||||
|
} elseif ($uriPath === 'settings/widgets') {
|
||||||
|
$target = $pagesBase . '/users/settings_widgets.php';
|
||||||
|
} elseif ($uriPath === 'settings/search-engines') {
|
||||||
|
$target = $pagesBase . '/users/settings_search_engines.php';
|
||||||
|
} elseif ($uriPath === 'settings/apps') {
|
||||||
|
$target = $pagesBase . '/users/settings_apps.php';
|
||||||
} elseif ($uriPath === 'users') {
|
} elseif ($uriPath === 'users') {
|
||||||
$target = $pagesBase . '/users/index.php';
|
$target = $pagesBase . '/users/index.php';
|
||||||
} elseif ($uriPath === 'dashboard') {
|
} elseif ($uriPath === 'dashboard') {
|
||||||
|
|||||||
@@ -230,6 +230,54 @@ final class BaseSchema
|
|||||||
)"
|
)"
|
||||||
);
|
);
|
||||||
$pdo->exec("CREATE INDEX IF NOT EXISTS nexus_dashboard_shares_dashboard_idx ON nexus_dashboard_shares (dashboard_id, share_type, share_value)");
|
$pdo->exec("CREATE INDEX IF NOT EXISTS nexus_dashboard_shares_dashboard_idx ON nexus_dashboard_shares (dashboard_id, share_type, share_value)");
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_widget_templates (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
owner_key TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
widget_type TEXT NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
visibility TEXT NOT NULL DEFAULT 'private',
|
||||||
|
config TEXT NOT NULL DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_search_engines (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
owner_key TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
short_code TEXT NOT NULL,
|
||||||
|
engine_type TEXT NOT NULL DEFAULT 'template',
|
||||||
|
template_url TEXT NULL,
|
||||||
|
integration_id BIGINT NULL,
|
||||||
|
visibility TEXT NOT NULL DEFAULT 'private',
|
||||||
|
is_default BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
config TEXT NOT NULL DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
$pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS nexus_search_engines_owner_short_idx ON nexus_search_engines (owner_key, short_code)");
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_apps (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
owner_key TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
app_url TEXT NOT NULL,
|
||||||
|
icon_url TEXT NULL,
|
||||||
|
integration_id BIGINT NULL,
|
||||||
|
visibility TEXT NOT NULL DEFAULT 'private',
|
||||||
|
config TEXT NOT NULL DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function ensureSqlite(\PDO $pdo): void
|
private static function ensureSqlite(\PDO $pdo): void
|
||||||
@@ -440,6 +488,54 @@ final class BaseSchema
|
|||||||
)"
|
)"
|
||||||
);
|
);
|
||||||
$pdo->exec("CREATE INDEX IF NOT EXISTS nexus_dashboard_shares_dashboard_idx ON nexus_dashboard_shares (dashboard_id, share_type, share_value)");
|
$pdo->exec("CREATE INDEX IF NOT EXISTS nexus_dashboard_shares_dashboard_idx ON nexus_dashboard_shares (dashboard_id, share_type, share_value)");
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_widget_templates (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
owner_key TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
widget_type TEXT NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
visibility TEXT NOT NULL DEFAULT 'private',
|
||||||
|
config TEXT NOT NULL DEFAULT '{}',
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_search_engines (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
owner_key TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
short_code TEXT NOT NULL,
|
||||||
|
engine_type TEXT NOT NULL DEFAULT 'template',
|
||||||
|
template_url TEXT NULL,
|
||||||
|
integration_id INTEGER NULL,
|
||||||
|
visibility TEXT NOT NULL DEFAULT 'private',
|
||||||
|
is_default INTEGER NOT NULL DEFAULT 0,
|
||||||
|
config TEXT NOT NULL DEFAULT '{}',
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
$pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS nexus_search_engines_owner_short_idx ON nexus_search_engines (owner_key, short_code)");
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_apps (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
owner_key TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
app_url TEXT NOT NULL,
|
||||||
|
icon_url TEXT NULL,
|
||||||
|
integration_id INTEGER NULL,
|
||||||
|
visibility TEXT NOT NULL DEFAULT 'private',
|
||||||
|
config TEXT NOT NULL DEFAULT '{}',
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function ensureGeneric(\PDO $pdo): void
|
private static function ensureGeneric(\PDO $pdo): void
|
||||||
@@ -650,6 +746,54 @@ final class BaseSchema
|
|||||||
KEY nexus_dashboard_shares_dashboard_idx (dashboard_id, share_type, share_value)
|
KEY nexus_dashboard_shares_dashboard_idx (dashboard_id, share_type, share_value)
|
||||||
)"
|
)"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_widget_templates (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
owner_key VARCHAR(190) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
widget_type VARCHAR(64) NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
visibility VARCHAR(32) NOT NULL DEFAULT 'private',
|
||||||
|
config TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_search_engines (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
owner_key VARCHAR(190) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
short_code VARCHAR(64) NOT NULL,
|
||||||
|
engine_type VARCHAR(32) NOT NULL DEFAULT 'template',
|
||||||
|
template_url TEXT NULL,
|
||||||
|
integration_id BIGINT NULL,
|
||||||
|
visibility VARCHAR(32) NOT NULL DEFAULT 'private',
|
||||||
|
is_default TINYINT NOT NULL DEFAULT 0,
|
||||||
|
config TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY nexus_search_engines_owner_short_idx (owner_key, short_code)
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_apps (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
owner_key VARCHAR(190) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
app_url TEXT NOT NULL,
|
||||||
|
icon_url TEXT NULL,
|
||||||
|
integration_id BIGINT NULL,
|
||||||
|
visibility VARCHAR(32) NOT NULL DEFAULT 'private',
|
||||||
|
config TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function seedTimezones(\PDO $pdo): void
|
private static function seedTimezones(\PDO $pdo): void
|
||||||
|
|||||||
@@ -108,6 +108,64 @@ final class NexusDashboardService
|
|||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function resolveHomeDashboard(?string $ownerKey, array $groups, bool $authenticated, int $preferredId = 0): ?array
|
||||||
|
{
|
||||||
|
if (!$this->available()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authenticated && $ownerKey !== null && $ownerKey !== '') {
|
||||||
|
if ($preferredId > 0) {
|
||||||
|
$preferred = $this->getDashboardById($preferredId);
|
||||||
|
if ($preferred !== null && $this->canAccessDashboard($preferred, $ownerKey, $groups)) {
|
||||||
|
return $preferred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dashboards = $this->listAccessibleDashboards($ownerKey, $groups);
|
||||||
|
foreach ($dashboards as $dashboard) {
|
||||||
|
if (!empty($dashboard['is_default']) && $this->canAccessDashboard($dashboard, $ownerKey, $groups)) {
|
||||||
|
return $dashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dashboards as $dashboard) {
|
||||||
|
if ($this->canAccessDashboard($dashboard, $ownerKey, $groups)) {
|
||||||
|
return $dashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$publicId = (int) (($this->settings()['global_home_dashboard_id'] ?? 0));
|
||||||
|
if ($publicId > 0) {
|
||||||
|
$dashboard = $this->getDashboardById($publicId);
|
||||||
|
if ($dashboard !== null && (string) ($dashboard['visibility'] ?? '') === 'public') {
|
||||||
|
return $dashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->listPublicDashboards() as $dashboard) {
|
||||||
|
return $dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listPublicDashboards(): array
|
||||||
|
{
|
||||||
|
if (!$this->available()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT *
|
||||||
|
FROM nexus_dashboards
|
||||||
|
WHERE visibility = 'public'
|
||||||
|
ORDER BY is_default DESC, sort_order ASC, title ASC, id ASC"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
return $this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []);
|
||||||
|
}
|
||||||
|
|
||||||
public function getDashboardById(int $id): ?array
|
public function getDashboardById(int $id): ?array
|
||||||
{
|
{
|
||||||
if (!$this->available() || $id <= 0) {
|
if (!$this->available() || $id <= 0) {
|
||||||
@@ -397,6 +455,63 @@ final class NexusDashboardService
|
|||||||
return $this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []);
|
return $this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function listAccessiblePageModules(string $ownerKey, array $groups = []): array
|
||||||
|
{
|
||||||
|
if (!$this->available()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($this->listPageModulesForOwner($ownerKey) as $module) {
|
||||||
|
$id = (int) ($module['id'] ?? 0);
|
||||||
|
if ($id > 0) {
|
||||||
|
$rows[] = $module;
|
||||||
|
$seen[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT *
|
||||||
|
FROM nexus_page_modules
|
||||||
|
WHERE visibility = 'public'
|
||||||
|
ORDER BY title ASC, id ASC"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
foreach ($this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []) as $module) {
|
||||||
|
$id = (int) ($module['id'] ?? 0);
|
||||||
|
if ($id > 0 && !isset($seen[$id])) {
|
||||||
|
$rows[] = $module;
|
||||||
|
$seen[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($groups as $group) {
|
||||||
|
$group = trim((string) $group);
|
||||||
|
if ($group === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT *
|
||||||
|
FROM nexus_page_modules
|
||||||
|
WHERE visibility = 'group'"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
foreach ($this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []) as $module) {
|
||||||
|
$id = (int) ($module['id'] ?? 0);
|
||||||
|
if ($id <= 0 || isset($seen[$id])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_array($group, (array) ($module['config']['groups'] ?? []), true)) {
|
||||||
|
$rows[] = $module;
|
||||||
|
$seen[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
public function createPageModule(string $ownerKey, array $data): int
|
public function createPageModule(string $ownerKey, array $data): int
|
||||||
{
|
{
|
||||||
if (!$this->available() || $ownerKey === '') {
|
if (!$this->available() || $ownerKey === '') {
|
||||||
@@ -466,6 +581,233 @@ final class NexusDashboardService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function listWidgetTemplates(string $ownerKey, bool $includePublic = true): array
|
||||||
|
{
|
||||||
|
if (!$this->available()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$clauses = [];
|
||||||
|
$params = [];
|
||||||
|
if ($ownerKey !== '') {
|
||||||
|
$clauses[] = 'owner_key = :owner_key';
|
||||||
|
$params['owner_key'] = $ownerKey;
|
||||||
|
}
|
||||||
|
if ($includePublic) {
|
||||||
|
$clauses[] = "visibility = 'public'";
|
||||||
|
}
|
||||||
|
if ($clauses === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM nexus_widget_templates WHERE ' . implode(' OR ', $clauses) . ' ORDER BY name ASC, id ASC'
|
||||||
|
);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []) as $row) {
|
||||||
|
$id = (int) ($row['id'] ?? 0);
|
||||||
|
if ($id > 0 && !isset($seen[$id])) {
|
||||||
|
$rows[] = $row;
|
||||||
|
$seen[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createWidgetTemplate(string $ownerKey, array $data): int
|
||||||
|
{
|
||||||
|
if (!$this->available() || $ownerKey === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"INSERT INTO nexus_widget_templates
|
||||||
|
(owner_key, name, widget_type, description, visibility, config, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:owner_key, :name, :widget_type, :description, :visibility, :config, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'owner_key' => $ownerKey,
|
||||||
|
'name' => trim((string) ($data['name'] ?? '')) ?: 'Widget',
|
||||||
|
'widget_type' => $this->normalizeItemType((string) ($data['widget_type'] ?? 'link')),
|
||||||
|
'description' => trim((string) ($data['description'] ?? '')),
|
||||||
|
'visibility' => $this->normalizeVisibility((string) ($data['visibility'] ?? 'private')),
|
||||||
|
'config' => $this->encodeJson((array) ($data['config'] ?? [])),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int) $this->pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteWidgetTemplate(int $id, string $ownerKey): void
|
||||||
|
{
|
||||||
|
if (!$this->available() || $id <= 0 || $ownerKey === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM nexus_widget_templates WHERE id = :id AND owner_key = :owner_key");
|
||||||
|
$stmt->execute(['id' => $id, 'owner_key' => $ownerKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listSearchEngines(string $ownerKey, bool $includePublic = true): array
|
||||||
|
{
|
||||||
|
if (!$this->available()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$clauses = [];
|
||||||
|
$params = [];
|
||||||
|
if ($ownerKey !== '') {
|
||||||
|
$clauses[] = 'owner_key = :owner_key';
|
||||||
|
$params['owner_key'] = $ownerKey;
|
||||||
|
}
|
||||||
|
if ($includePublic) {
|
||||||
|
$clauses[] = "visibility = 'public'";
|
||||||
|
}
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM nexus_search_engines WHERE ' . implode(' OR ', $clauses) . ' ORDER BY is_default DESC, name ASC, id ASC'
|
||||||
|
);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []) as $row) {
|
||||||
|
$id = (int) ($row['id'] ?? 0);
|
||||||
|
if ($id > 0 && !isset($seen[$id])) {
|
||||||
|
$rows[] = $row;
|
||||||
|
$seen[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createSearchEngine(string $ownerKey, array $data): int
|
||||||
|
{
|
||||||
|
if (!$this->available() || $ownerKey === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortCode = $this->normalizeShortCode((string) ($data['short_code'] ?? ''));
|
||||||
|
if ($shortCode === '') {
|
||||||
|
$shortCode = 's' . random_int(100, 999);
|
||||||
|
}
|
||||||
|
$shortCode = $this->uniqueSearchShortCode($ownerKey, $shortCode, 0);
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"INSERT INTO nexus_search_engines
|
||||||
|
(owner_key, name, short_code, engine_type, template_url, integration_id, visibility, is_default, config, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:owner_key, :name, :short_code, :engine_type, :template_url, :integration_id, :visibility, :is_default, :config, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'owner_key' => $ownerKey,
|
||||||
|
'name' => trim((string) ($data['name'] ?? '')) ?: 'Suche',
|
||||||
|
'short_code' => $shortCode,
|
||||||
|
'engine_type' => trim((string) ($data['engine_type'] ?? 'template')) ?: 'template',
|
||||||
|
'template_url' => trim((string) ($data['template_url'] ?? '')),
|
||||||
|
'integration_id' => !empty($data['integration_id']) ? (int) $data['integration_id'] : null,
|
||||||
|
'visibility' => $this->normalizeVisibility((string) ($data['visibility'] ?? 'private')),
|
||||||
|
'is_default' => !empty($data['is_default']) ? 1 : 0,
|
||||||
|
'config' => $this->encodeJson((array) ($data['config'] ?? [])),
|
||||||
|
]);
|
||||||
|
$id = (int) $this->pdo->lastInsertId();
|
||||||
|
if (!empty($data['is_default'])) {
|
||||||
|
$this->setDefaultSearchEngine($ownerKey, $id);
|
||||||
|
}
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDefaultSearchEngine(string $ownerKey, int $id): void
|
||||||
|
{
|
||||||
|
if (!$this->available() || $ownerKey === '' || $id <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$stmt = $this->pdo->prepare("UPDATE nexus_search_engines SET is_default = 0, updated_at = CURRENT_TIMESTAMP WHERE owner_key = :owner_key");
|
||||||
|
$stmt->execute(['owner_key' => $ownerKey]);
|
||||||
|
$stmt = $this->pdo->prepare("UPDATE nexus_search_engines SET is_default = 1, updated_at = CURRENT_TIMESTAMP WHERE id = :id AND owner_key = :owner_key");
|
||||||
|
$stmt->execute(['id' => $id, 'owner_key' => $ownerKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteSearchEngine(int $id, string $ownerKey): void
|
||||||
|
{
|
||||||
|
if (!$this->available() || $id <= 0 || $ownerKey === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM nexus_search_engines WHERE id = :id AND owner_key = :owner_key");
|
||||||
|
$stmt->execute(['id' => $id, 'owner_key' => $ownerKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listApps(string $ownerKey, bool $includePublic = true): array
|
||||||
|
{
|
||||||
|
if (!$this->available()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$clauses = [];
|
||||||
|
$params = [];
|
||||||
|
if ($ownerKey !== '') {
|
||||||
|
$clauses[] = 'owner_key = :owner_key';
|
||||||
|
$params['owner_key'] = $ownerKey;
|
||||||
|
}
|
||||||
|
if ($includePublic) {
|
||||||
|
$clauses[] = "visibility = 'public'";
|
||||||
|
}
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM nexus_apps WHERE ' . implode(' OR ', $clauses) . ' ORDER BY name ASC, id ASC'
|
||||||
|
);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($this->hydrateRows($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []) as $row) {
|
||||||
|
$id = (int) ($row['id'] ?? 0);
|
||||||
|
if ($id > 0 && !isset($seen[$id])) {
|
||||||
|
$rows[] = $row;
|
||||||
|
$seen[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createApp(string $ownerKey, array $data): int
|
||||||
|
{
|
||||||
|
if (!$this->available() || $ownerKey === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"INSERT INTO nexus_apps
|
||||||
|
(owner_key, name, description, app_url, icon_url, integration_id, visibility, config, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:owner_key, :name, :description, :app_url, :icon_url, :integration_id, :visibility, :config, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'owner_key' => $ownerKey,
|
||||||
|
'name' => trim((string) ($data['name'] ?? '')) ?: 'App',
|
||||||
|
'description' => trim((string) ($data['description'] ?? '')),
|
||||||
|
'app_url' => trim((string) ($data['app_url'] ?? '')),
|
||||||
|
'icon_url' => trim((string) ($data['icon_url'] ?? '')),
|
||||||
|
'integration_id' => !empty($data['integration_id']) ? (int) $data['integration_id'] : null,
|
||||||
|
'visibility' => $this->normalizeVisibility((string) ($data['visibility'] ?? 'private')),
|
||||||
|
'config' => $this->encodeJson((array) ($data['config'] ?? [])),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int) $this->pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteApp(int $id, string $ownerKey): void
|
||||||
|
{
|
||||||
|
if (!$this->available() || $id <= 0 || $ownerKey === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM nexus_apps WHERE id = :id AND owner_key = :owner_key");
|
||||||
|
$stmt->execute(['id' => $id, 'owner_key' => $ownerKey]);
|
||||||
|
}
|
||||||
|
|
||||||
private function listSharedDashboardIds(string $ownerKey, array $groups): array
|
private function listSharedDashboardIds(string $ownerKey, array $groups): array
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
@@ -566,7 +908,7 @@ final class NexusDashboardService
|
|||||||
private function normalizeItemType(string $value): string
|
private function normalizeItemType(string $value): string
|
||||||
{
|
{
|
||||||
$value = trim($value);
|
$value = trim($value);
|
||||||
return in_array($value, ['link', 'iframe', 'page_module', 'bookmark_group', 'module_link'], true) ? $value : 'link';
|
return in_array($value, ['link', 'iframe', 'page_module', 'bookmark_group', 'module_link', 'app', 'widget_template'], true) ? $value : 'link';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizePageModuleType(string $value): string
|
private function normalizePageModuleType(string $value): string
|
||||||
@@ -625,4 +967,59 @@ final class NexusDashboardService
|
|||||||
}
|
}
|
||||||
return (int) $value;
|
return (int) $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function settings(): array
|
||||||
|
{
|
||||||
|
return function_exists('nexus_settings') ? nexus_settings() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canAccessDashboard(array $dashboard, string $ownerKey, array $groups): bool
|
||||||
|
{
|
||||||
|
if ((string) ($dashboard['owner_key'] ?? '') === $ownerKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$visibility = (string) ($dashboard['visibility'] ?? 'private');
|
||||||
|
if ($visibility === 'public') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($visibility === 'group') {
|
||||||
|
$sharedIds = $this->listSharedDashboardIds($ownerKey, $groups);
|
||||||
|
return in_array((int) ($dashboard['id'] ?? 0), $sharedIds, true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeShortCode(string $value): string
|
||||||
|
{
|
||||||
|
$value = strtolower(trim($value));
|
||||||
|
$value = preg_replace('/[^a-z0-9_-]+/', '', $value) ?? '';
|
||||||
|
return substr($value, 0, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function uniqueSearchShortCode(string $ownerKey, string $candidate, int $ignoreId): string
|
||||||
|
{
|
||||||
|
$shortCode = $candidate;
|
||||||
|
$suffix = 1;
|
||||||
|
while ($this->searchShortCodeExists($ownerKey, $shortCode, $ignoreId)) {
|
||||||
|
$suffix++;
|
||||||
|
$shortCode = substr($candidate, 0, 20) . $suffix;
|
||||||
|
}
|
||||||
|
return $shortCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function searchShortCodeExists(string $ownerKey, string $shortCode, int $ignoreId): bool
|
||||||
|
{
|
||||||
|
$sql = "SELECT id FROM nexus_search_engines WHERE owner_key = :owner_key AND short_code = :short_code";
|
||||||
|
if ($ignoreId > 0) {
|
||||||
|
$sql .= " AND id <> :id";
|
||||||
|
}
|
||||||
|
$sql .= " LIMIT 1";
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$params = ['owner_key' => $ownerKey, 'short_code' => $shortCode];
|
||||||
|
if ($ignoreId > 0) {
|
||||||
|
$params['id'] = $ignoreId;
|
||||||
|
}
|
||||||
|
$stmt->execute($params);
|
||||||
|
return (bool) $stmt->fetchColumn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user