Next
adas
This commit is contained in:
14
public/api/fx-rates/index.php
Normal file
14
public/api/fx-rates/index.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php';
|
||||
|
||||
use ModulesCore\ModuleHttp;
|
||||
|
||||
session_start();
|
||||
|
||||
$projectRoot = dirname(__DIR__, 3);
|
||||
ModuleHttp::requireDesktopAccess($projectRoot, true);
|
||||
|
||||
require $projectRoot . '/modules/fx-rates/api/index.php';
|
||||
5
public/apps/fx-rates/index.php
Normal file
5
public/apps/fx-rates/index.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__, 3) . '/modules/fx-rates/pages/index.php';
|
||||
@@ -286,6 +286,9 @@ h1 {
|
||||
|
||||
.widget-card-actions {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.widget-card-action {
|
||||
@@ -306,6 +309,30 @@ h1 {
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
.widget-card-action--secondary {
|
||||
background: rgba(15, 23, 42, 0.16);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.widget-card-action--secondary:hover {
|
||||
background: rgba(15, 23, 42, 0.24);
|
||||
}
|
||||
|
||||
.widget-card-status,
|
||||
.widget-card-message {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.widget-card-status {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.widget-card-message {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.widget-badge {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
|
||||
@@ -481,18 +481,11 @@ if (payloadNode) {
|
||||
`;
|
||||
}
|
||||
|
||||
if (definition.content_mode === 'native-module' && definition.app_id === 'mining-checker') {
|
||||
if (definition.content_mode === 'native-module') {
|
||||
const hostId = `${String(definition.app_id || 'module').replace(/[^a-z0-9_-]/gi, '-')}-app`;
|
||||
return `
|
||||
<div class="window-content window-content--embedded window-content--module">
|
||||
<div id="mining-checker-app" class="window-module-host" data-module-app="mining-checker"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (definition.content_mode === 'native-module' && definition.app_id === 'user-self-management') {
|
||||
return `
|
||||
<div class="window-content window-content--embedded window-content--module">
|
||||
<div id="user-self-management-app" class="window-module-host" data-module-app="user-self-management"></div>
|
||||
<div id="${hostId}" class="window-module-host" data-module-app="${escapeHtml(definition.app_id || 'module')}"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -1434,6 +1427,82 @@ if (payloadNode) {
|
||||
});
|
||||
};
|
||||
|
||||
const renderFxRatesWidget = async (card, widget) => {
|
||||
const statusNode = card.querySelector('[data-widget-status]');
|
||||
const messageNode = card.querySelector('[data-widget-message]');
|
||||
const refreshButton = card.querySelector('[data-widget-refresh]');
|
||||
const forceButton = card.querySelector('[data-widget-force-refresh]');
|
||||
|
||||
const loadStatus = async () => {
|
||||
if (!(statusNode instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
statusNode.textContent = 'Kursstatus wird geladen ...';
|
||||
try {
|
||||
const response = await fetch(widget.status_api, {
|
||||
credentials: 'same-origin',
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
const rows = Array.isArray(payload?.data) ? payload.data : [];
|
||||
const latest = rows.slice().sort((left, right) => {
|
||||
const leftTs = new Date(left?.fetched_at || 0).getTime();
|
||||
const rightTs = new Date(right?.fetched_at || 0).getTime();
|
||||
return rightTs - leftTs;
|
||||
})[0] || null;
|
||||
statusNode.textContent = latest
|
||||
? `Letzter Abruf: ${latest.fetched_at_display || latest.fetched_at} · ${latest.base_currency} · ${latest.provider}`
|
||||
: 'Noch keine gespeicherten Kurse vorhanden.';
|
||||
} catch (_error) {
|
||||
statusNode.textContent = 'Kursstatus konnte nicht geladen werden.';
|
||||
}
|
||||
};
|
||||
|
||||
const runRefresh = async (force) => {
|
||||
if (!(messageNode instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageNode.textContent = 'Aktualisierung laeuft ...';
|
||||
try {
|
||||
const response = await fetch(widget.refresh_api, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
force: force === true,
|
||||
trigger_source: 'widget',
|
||||
}),
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(payload?.context?.message || payload?.error || `HTTP ${response.status}`);
|
||||
}
|
||||
const result = payload?.data || {};
|
||||
messageNode.textContent = result.reused
|
||||
? 'Kein neuer API-Abruf. Der letzte gespeicherte Snapshot ist noch jung genug.'
|
||||
: `Kurse aktualisiert. ${result.updated_count || 0} Werte gespeichert.`;
|
||||
await loadStatus();
|
||||
} catch (error) {
|
||||
messageNode.textContent = error instanceof Error ? error.message : 'Aktualisierung fehlgeschlagen.';
|
||||
}
|
||||
};
|
||||
|
||||
refreshButton?.addEventListener('click', () => runRefresh(false));
|
||||
forceButton?.addEventListener('click', () => {
|
||||
const confirmed = window.confirm('Der Kursabruf wird jetzt trotz jungem Snapshot erzwungen. Wirklich fortfahren?');
|
||||
if (confirmed) {
|
||||
runRefresh(true);
|
||||
}
|
||||
});
|
||||
|
||||
await loadStatus();
|
||||
};
|
||||
|
||||
const renderWidgets = () => {
|
||||
if (!widgetZoneRoot) {
|
||||
return;
|
||||
@@ -1455,6 +1524,17 @@ if (payloadNode) {
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
const widgetBodyMarkup = widget.widget_type === 'fx-rates-refresh'
|
||||
? `
|
||||
<div class="widget-card-status" data-widget-status>Wird geladen ...</div>
|
||||
<p class="widget-card-message" data-widget-message>${widget.content || ''}</p>
|
||||
<div class="widget-card-actions">
|
||||
<button class="widget-card-action" type="button" data-widget-refresh>Kurse aktualisieren</button>
|
||||
<button class="widget-card-action widget-card-action--secondary" type="button" data-widget-force-refresh>Force</button>
|
||||
${widget.launch_app_id ? `<button class="widget-card-action widget-card-action--secondary" type="button" data-app-id="${escapeHtml(widget.launch_app_id)}">${escapeHtml(widget.action_label || 'Oeffnen')}</button>` : ''}
|
||||
</div>
|
||||
`
|
||||
: `<p>${widget.content || ''}</p>${actionMarkup}`;
|
||||
card.innerHTML = `
|
||||
<div class="widget-card-header">
|
||||
<div>
|
||||
@@ -1463,21 +1543,23 @@ if (payloadNode) {
|
||||
</div>
|
||||
<span class="widget-badge">${widget.icon || 'WG'}</span>
|
||||
</div>
|
||||
<p>${widget.content || ''}</p>
|
||||
${actionMarkup}
|
||||
${widgetBodyMarkup}
|
||||
`;
|
||||
|
||||
const actionButton = card.querySelector('.widget-card-action');
|
||||
if (actionButton instanceof HTMLButtonElement && widget.launch_app_id) {
|
||||
actionButton.addEventListener('click', () => {
|
||||
const app = findAppById(widget.launch_app_id);
|
||||
card.querySelectorAll('[data-app-id]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const appId = button.getAttribute('data-app-id') || '';
|
||||
const app = findAppById(appId);
|
||||
if (app) {
|
||||
openApp(app);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
widgetZoneRoot.appendChild(card);
|
||||
if (widget.widget_type === 'fx-rates-refresh') {
|
||||
renderFxRatesWidget(card, widget);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1758,6 +1840,18 @@ if (payloadNode) {
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('desktop:open-app', (event) => {
|
||||
const appId = typeof event.detail?.appId === 'string' ? event.detail.appId : '';
|
||||
if (!appId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const app = findAppById(appId);
|
||||
if (app) {
|
||||
openApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
renderWidgets();
|
||||
renderStartMenu();
|
||||
updateDebugToggleUi();
|
||||
|
||||
Reference in New Issue
Block a user