New desktop
This commit is contained in:
31
temp/nexus-module-import/modules/fx-rates/pages/asset.php
Normal file
31
temp/nexus-module-import/modules/fx-rates/pages/asset.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
$file = (string) ($_GET['file'] ?? '');
|
||||
$base = realpath(__DIR__ . '/../assets');
|
||||
$map = [
|
||||
'fx-rates.css' => $base . '/fx-rates.css',
|
||||
'fx-rates.js' => $base . '/fx-rates.js',
|
||||
'fx-rates-currencies.js' => $base . '/fx-rates-currencies.js',
|
||||
];
|
||||
|
||||
if (!isset($map[$file])) {
|
||||
http_response_code(404);
|
||||
exit('Not found');
|
||||
}
|
||||
|
||||
$path = $map[$file];
|
||||
if (!$base || !is_file($path) || !str_starts_with($path, $base)) {
|
||||
http_response_code(404);
|
||||
exit('Not found');
|
||||
}
|
||||
|
||||
$ext = pathinfo($path, PATHINFO_EXTENSION);
|
||||
if ($ext === 'css') {
|
||||
header('Content-Type: text/css; charset=utf-8');
|
||||
} elseif ($ext === 'js') {
|
||||
header('Content-Type: application/javascript; charset=utf-8');
|
||||
} else {
|
||||
header('Content-Type: application/octet-stream');
|
||||
}
|
||||
|
||||
readfile($path);
|
||||
exit;
|
||||
280
temp/nexus-module-import/modules/fx-rates/pages/currencies.php
Normal file
280
temp/nexus-module-import/modules/fx-rates/pages/currencies.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once dirname(__DIR__) . '/bootstrap.php';
|
||||
|
||||
$assets = app()->assets();
|
||||
if ($assets) {
|
||||
$assets->addStyle('/module/fx-rates/asset?file=fx-rates.css');
|
||||
$assets->addScript('/module/fx-rates/asset?file=fx-rates-currencies.js', 'footer', true);
|
||||
}
|
||||
|
||||
$settings = module_fn('fx-rates', 'settings');
|
||||
$service = module_fn('fx-rates', 'service');
|
||||
$notice = trim((string) ($_GET['notice'] ?? ''));
|
||||
$error = trim((string) ($_GET['error'] ?? ''));
|
||||
|
||||
if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') {
|
||||
try {
|
||||
$action = trim((string) ($_POST['fx_action'] ?? ''));
|
||||
if ($action === 'save_selection') {
|
||||
$payload = [
|
||||
'display_base_currency' => (string) ($_POST['display_base_currency'] ?? ''),
|
||||
'preferred_currencies' => $_POST['preferred_currencies'] ?? [],
|
||||
];
|
||||
$saved = module_fn('fx-rates', 'save_runtime_settings', $payload);
|
||||
$params = ['notice' => 'Waehrungs-Auswahl gespeichert.'];
|
||||
if (is_array($saved) && !empty($saved['display_base_currency'])) {
|
||||
$params['base'] = (string) $saved['display_base_currency'];
|
||||
}
|
||||
redirect('/module/fx-rates/currencies?' . http_build_query($params));
|
||||
}
|
||||
|
||||
if ($action === 'sync_catalog') {
|
||||
$result = module_fn('fx-rates', 'run_setup_action', 'sync_currency_catalog');
|
||||
redirect('/module/fx-rates/currencies?' . http_build_query([
|
||||
'notice' => sprintf('Waehrungskatalog synchronisiert. %d Waehrungen verarbeitet.', (int) ($result['synced_count'] ?? 0)),
|
||||
]));
|
||||
}
|
||||
|
||||
if ($action === 'refresh_rates') {
|
||||
$result = $service->refreshLatestRates(null, (string) ($settings['default_base_currency'] ?? ''), 'manual');
|
||||
redirect('/module/fx-rates/currencies?' . http_build_query([
|
||||
'notice' => sprintf('Alle Wechselkurse aktualisiert. %d Werte gespeichert.', (int) ($result['updated_count'] ?? 0)),
|
||||
]));
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
redirect('/module/fx-rates/currencies?' . http_build_query([
|
||||
'error' => $exception->getMessage() !== '' ? $exception->getMessage() : 'Aktion konnte nicht ausgefuehrt werden.',
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$catalog = is_array($settings['currency_catalog'] ?? null) ? $settings['currency_catalog'] : [];
|
||||
$preferredCurrencies = is_array($settings['preferred_currencies'] ?? null) ? $settings['preferred_currencies'] : [];
|
||||
$savedDisplayBaseCurrency = strtoupper(trim((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? 'EUR')));
|
||||
$requestedDisplayBaseCurrency = strtoupper(trim((string) ($_GET['base'] ?? '')));
|
||||
$latest = $service->latestStatus();
|
||||
$recentFetches = $service->recentFetches(15);
|
||||
|
||||
$currencies = [];
|
||||
foreach ($catalog as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$code = strtoupper(trim((string) ($item['code'] ?? '')));
|
||||
$name = trim((string) ($item['name'] ?? ''));
|
||||
if ($code === '' || $name === '') {
|
||||
continue;
|
||||
}
|
||||
$currencies[] = [
|
||||
'code' => $code,
|
||||
'name' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
$cryptoCodes = array_fill_keys([
|
||||
'ADA', 'ARB', 'BNB', 'BTC', 'DAI', 'DOGE', 'DOT', 'ETH', 'LINK', 'LTC',
|
||||
'SOL', 'USDC', 'USDT', 'XAG', 'XAU', 'XRP',
|
||||
], true);
|
||||
$fiatCount = 0;
|
||||
$cryptoCount = 0;
|
||||
foreach ($currencies as $currency) {
|
||||
if (isset($cryptoCodes[$currency['code']])) {
|
||||
$cryptoCount++;
|
||||
} else {
|
||||
$fiatCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$catalogCodes = [];
|
||||
foreach ($currencies as $currency) {
|
||||
$catalogCodes[(string) $currency['code']] = true;
|
||||
}
|
||||
|
||||
$displayBaseCurrency = $requestedDisplayBaseCurrency !== '' ? $requestedDisplayBaseCurrency : $savedDisplayBaseCurrency;
|
||||
if ($displayBaseCurrency === '' || (!isset($catalogCodes[$displayBaseCurrency]) && $preferredCurrencies !== [])) {
|
||||
$displayBaseCurrency = $savedDisplayBaseCurrency !== '' ? $savedDisplayBaseCurrency : (string) ($preferredCurrencies[0] ?? 'EUR');
|
||||
}
|
||||
|
||||
$tableCurrencies = [];
|
||||
foreach ([$displayBaseCurrency, ...$preferredCurrencies] as $currency) {
|
||||
$currency = strtoupper(trim((string) $currency));
|
||||
if ($currency !== '' && !in_array($currency, $tableCurrencies, true)) {
|
||||
$tableCurrencies[] = $currency;
|
||||
}
|
||||
}
|
||||
|
||||
$currencyPageData = json_encode([
|
||||
'currencies' => $currencies,
|
||||
'preferred_currencies' => array_values(array_unique(array_map(static fn (mixed $code): string => strtoupper(trim((string) $code)), $preferredCurrencies))),
|
||||
'display_base_currency' => $displayBaseCurrency,
|
||||
'saved_display_base_currency' => $savedDisplayBaseCurrency,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$tabs = [
|
||||
['label' => 'Ueberblick', 'href' => '/module/fx-rates'],
|
||||
['label' => 'Waehrungen', 'href' => '/module/fx-rates/currencies', 'active' => true],
|
||||
];
|
||||
?>
|
||||
<?= module_shell_header('fx-rates', [
|
||||
'title' => 'Waehrungen',
|
||||
'tabs' => $tabs,
|
||||
'actions' => [
|
||||
['label' => 'Nexus Übersicht', 'href' => '/', 'variant' => 'secondary', 'size' => 'sm'],
|
||||
['label' => 'Setup', 'href' => '/modules/setup/fx-rates', 'variant' => 'secondary', 'size' => 'sm'],
|
||||
],
|
||||
]) ?>
|
||||
<div id="fx-rates-currencies" data-page='<?= e(is_string($currencyPageData) ? $currencyPageData : '{}') ?>'>
|
||||
<?php if ($notice !== ''): ?>
|
||||
<section class="section-box">
|
||||
<div class="fx-message is-success"><?= e($notice) ?></div>
|
||||
</section>
|
||||
<?php elseif ($error !== ''): ?>
|
||||
<section class="section-box">
|
||||
<div class="fx-message is-error"><?= e($error) ?></div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="section-box">
|
||||
<div class="fx-section-head">
|
||||
<div>
|
||||
<h2>Waehrungs-Update</h2>
|
||||
<p>Auswahl wird in den Waehrungskurs-Einstellungen gespeichert und steht damit auf Handy und Desktop gleich zur Verfuegung.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fx-action-row">
|
||||
<form method="post">
|
||||
<input type="hidden" name="fx_action" value="refresh_rates">
|
||||
<button type="submit" class="module-button module-button--primary">Alle Wechselkurse aktualisieren</button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="fx_action" value="sync_catalog">
|
||||
<button type="submit" class="module-button module-button--ghost">Waehrungskatalog sync</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="fx-card-grid">
|
||||
<section class="card-box">
|
||||
<div class="fx-mini-label">Fiat</div>
|
||||
<div class="fx-card-value"><?= e((string) $fiatCount) ?> Waehrungen</div>
|
||||
</section>
|
||||
<section class="card-box">
|
||||
<div class="fx-mini-label">Krypto</div>
|
||||
<div class="fx-card-value"><?= e((string) $cryptoCount) ?> Waehrungen</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="section-box">
|
||||
<div class="fx-field-label">Bevorzugte Waehrungen fuer Anzeige</div>
|
||||
<div class="fx-currency-selection-row">
|
||||
<div class="fx-token-list fx-token-list--inline" data-fx-token-list></div>
|
||||
<div class="fx-currency-search">
|
||||
<input type="text" class="fx-input" value="" placeholder="Waehrung hinzufuegen: EUR, USD, DOGE oder Euro" autocomplete="off" data-fx-search-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fx-suggestion-list" data-fx-suggestions></div>
|
||||
|
||||
<div class="fx-field fx-currency-search">
|
||||
<label class="fx-field-label" for="fx-display-base-select">Darstellung auf Basis von</label>
|
||||
<select id="fx-display-base-select" class="fx-select" data-fx-display-base-select></select>
|
||||
</div>
|
||||
|
||||
<div class="fx-save-row">
|
||||
<form method="post">
|
||||
<input type="hidden" name="fx_action" value="save_selection">
|
||||
<input type="hidden" name="display_base_currency" value="<?= e($displayBaseCurrency) ?>" data-fx-display-base-hidden>
|
||||
<div data-fx-hidden-preferred></div>
|
||||
<button type="submit" class="module-button module-button--ghost">Auswahl speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-box">
|
||||
<div class="fx-card-head">
|
||||
<div>
|
||||
<h2>Letzte 15 Kurs-Uploads</h2>
|
||||
<p>Zeigt die zuletzt gespeicherten Wechselkurse aus der Datenbank.</p>
|
||||
</div>
|
||||
<div class="fx-card-meta">
|
||||
<div><strong>Anzeige-Basis:</strong> <?= e($displayBaseCurrency) ?></div>
|
||||
<div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fx-table-wrap">
|
||||
<table class="fx-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zeit</th>
|
||||
<?php foreach ($tableCurrencies as $currency): ?>
|
||||
<th><?= e((string) $currency) ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($recentFetches === []): ?>
|
||||
<tr><td colspan="<?= 1 + count($tableCurrencies) ?>">Noch keine Abrufe vorhanden.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($recentFetches as $fetch): ?>
|
||||
<?php
|
||||
$fetchBaseCurrency = strtoupper(trim((string) ($fetch['base_currency'] ?? '')));
|
||||
$snapshot = $service->snapshotByFetchId((int) ($fetch['id'] ?? 0), $fetchBaseCurrency, $tableCurrencies);
|
||||
$originalRates = is_array($snapshot['rates'] ?? null) ? $snapshot['rates'] : [];
|
||||
$displayBaseRate = $displayBaseCurrency === $fetchBaseCurrency
|
||||
? 1.0
|
||||
: (is_numeric($originalRates[$displayBaseCurrency] ?? null) ? (float) $originalRates[$displayBaseCurrency] : null);
|
||||
$tableRates = [];
|
||||
foreach ($tableCurrencies as $currency) {
|
||||
$currency = strtoupper(trim((string) $currency));
|
||||
if ($currency === '') {
|
||||
continue;
|
||||
}
|
||||
if ($currency === $displayBaseCurrency) {
|
||||
$tableRates[$currency] = 1.0;
|
||||
continue;
|
||||
}
|
||||
if ($displayBaseRate === null || $displayBaseRate <= 0) {
|
||||
$tableRates[$currency] = null;
|
||||
continue;
|
||||
}
|
||||
if ($currency === $fetchBaseCurrency) {
|
||||
$tableRates[$currency] = 1 / $displayBaseRate;
|
||||
continue;
|
||||
}
|
||||
$rawRate = $originalRates[$currency] ?? null;
|
||||
$tableRates[$currency] = is_numeric($rawRate) ? ((float) $rawRate / $displayBaseRate) : null;
|
||||
}
|
||||
$infoTitle = sprintf(
|
||||
'Basis: %s | Provider: %s | Ausloeser: %s',
|
||||
(string) ($fetch['base_currency'] ?? '-'),
|
||||
(string) ($fetch['provider'] ?? '-'),
|
||||
(string) ($fetch['trigger_source_label'] ?? $fetch['trigger_source'] ?? '-')
|
||||
);
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fx-history-date">
|
||||
<span><?= e((string) ($fetch['fetched_at_display'] ?? $fetch['fetched_at'] ?? '')) ?></span>
|
||||
<button
|
||||
type="button"
|
||||
class="fx-info-button"
|
||||
title="<?= e($infoTitle) ?>"
|
||||
aria-label="<?= e('Abrufinfo fuer ' . (string) ($fetch['fetched_at_display'] ?? $fetch['fetched_at'] ?? '')) ?>"
|
||||
>i</button>
|
||||
</div>
|
||||
</td>
|
||||
<?php foreach ($tableCurrencies as $currency): ?>
|
||||
<?php $value = $tableRates[(string) $currency] ?? null; ?>
|
||||
<td><?= is_numeric($value) ? e(number_format((float) $value, 8, ',', '')) : '–' ?></td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= module_shell_footer() ?>
|
||||
149
temp/nexus-module-import/modules/fx-rates/pages/index.php
Normal file
149
temp/nexus-module-import/modules/fx-rates/pages/index.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once dirname(__DIR__) . '/bootstrap.php';
|
||||
|
||||
$assets = app()->assets();
|
||||
if ($assets) {
|
||||
$assets->addStyle('/module/fx-rates/asset?file=fx-rates.css');
|
||||
$assets->addScript('/module/fx-rates/asset?file=fx-rates.js', 'footer', true);
|
||||
}
|
||||
|
||||
$settings = module_fn('fx-rates', 'settings');
|
||||
$service = module_fn('fx-rates', 'service');
|
||||
$preferredCurrencies = is_array($settings['preferred_currencies'] ?? null) ? $settings['preferred_currencies'] : [];
|
||||
$apiDescribeUrl = APP_API_BASE . '/fx-rates/v1/endpoints';
|
||||
$notice = trim((string) ($_GET['notice'] ?? ''));
|
||||
$error = trim((string) ($_GET['error'] ?? ''));
|
||||
|
||||
if ((string) ($_GET['refresh'] ?? '') === '1') {
|
||||
try {
|
||||
$force = !empty($_GET['force']);
|
||||
if ($force) {
|
||||
$result = $service->refreshLatestRates(null, (string) ($settings['default_base_currency'] ?? ''), 'manual');
|
||||
} else {
|
||||
$result = $service->autoRefreshLatestRates(
|
||||
(string) ($settings['default_base_currency'] ?? ''),
|
||||
null,
|
||||
(int) ($settings['refresh_max_age_minutes'] ?? 60),
|
||||
'manual'
|
||||
);
|
||||
}
|
||||
|
||||
$params = !empty($result['reused'])
|
||||
? [
|
||||
'notice' => sprintf(
|
||||
'Kein neuer API-Abruf. Der letzte gespeicherte Snapshot ist juenger als %d Minuten. Fuer einen erzwungenen Abruf bitte bestaetigen.',
|
||||
(int) ($settings['refresh_max_age_minutes'] ?? 60)
|
||||
),
|
||||
]
|
||||
: [
|
||||
'notice' => sprintf(
|
||||
'Aktuelle Kurse gespeichert. %d Werte aktualisiert.',
|
||||
(int) ($result['updated_count'] ?? 0)
|
||||
),
|
||||
];
|
||||
} catch (\Throwable $exception) {
|
||||
$params = ['error' => $exception->getMessage() !== '' ? $exception->getMessage() : 'Kurse konnten nicht aktualisiert werden.'];
|
||||
}
|
||||
|
||||
redirect('/module/fx-rates?' . http_build_query($params));
|
||||
}
|
||||
|
||||
$latest = $service->latestStatus();
|
||||
$recentFetches = $service->recentFetches(15);
|
||||
$pageData = json_encode([
|
||||
'settings' => $settings,
|
||||
'latest' => $latest,
|
||||
'preferred_currencies' => $preferredCurrencies,
|
||||
'recent_fetches' => $recentFetches,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$tabs = [
|
||||
['label' => 'Ueberblick', 'href' => '/module/fx-rates', 'active' => true],
|
||||
['label' => 'Waehrungen', 'href' => '/module/fx-rates/currencies'],
|
||||
];
|
||||
?>
|
||||
<?= module_shell_header('fx-rates', [
|
||||
'title' => 'Ueberblick',
|
||||
'tabs' => $tabs,
|
||||
'actions' => [
|
||||
['label' => 'Nexus Übersicht', 'href' => '/', 'variant' => 'secondary', 'size' => 'sm'],
|
||||
['label' => 'Setup', 'href' => '/modules/setup/fx-rates', 'variant' => 'secondary', 'size' => 'sm'],
|
||||
['label' => 'Aktuelle Kurse abrufen', 'href' => '/module/fx-rates?refresh=1', 'variant' => 'secondary', 'size' => 'sm'],
|
||||
],
|
||||
]) ?>
|
||||
<div id="fx-rates-app" data-page='<?= e(is_string($pageData) ? $pageData : '{}') ?>'>
|
||||
<?php if ($notice !== ''): ?>
|
||||
<section class="section-box">
|
||||
<div class="fx-message is-success"><?= e($notice) ?></div>
|
||||
</section>
|
||||
<?php elseif ($error !== ''): ?>
|
||||
<section class="section-box">
|
||||
<div class="fx-message is-error"><?= e($error) ?></div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="section-box">
|
||||
<div class="fx-section-head">
|
||||
<div>
|
||||
<h2>Umrechnung</h2>
|
||||
<p>Umrechnung auf Basis des letzten verfuegbaren Kurses zwischen den bevorzugten Waehrungen.</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="fx-api-note">
|
||||
API-Self-Describe-Endpoint:
|
||||
<a href="<?= e($apiDescribeUrl) ?>" target="_blank" rel="noopener noreferrer"><?= e($apiDescribeUrl) ?></a>
|
||||
</p>
|
||||
<div class="fx-form-grid">
|
||||
<label>
|
||||
<span>Quellwaehrung</span>
|
||||
<select name="convert_from">
|
||||
<?php foreach ($preferredCurrencies as $currency): ?>
|
||||
<option value="<?= e((string) $currency) ?>"><?= e((string) $currency) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Zielwaehrung</span>
|
||||
<select name="convert_to">
|
||||
<?php foreach ($preferredCurrencies as $currency): ?>
|
||||
<option value="<?= e((string) $currency) ?>" <?= (string) $currency === (string) ($preferredCurrencies[1] ?? $preferredCurrencies[0] ?? '') ? 'selected' : '' ?>><?= e((string) $currency) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Betrag</span>
|
||||
<input type="number" name="convert_amount" min="0" step="0.00000001" value="1">
|
||||
</label>
|
||||
</div>
|
||||
<div class="fx-convert-result" data-bind="convert-result">Noch keine Umrechnung berechnet.</div>
|
||||
</section>
|
||||
|
||||
<section class="section-box">
|
||||
<div class="fx-card-head">
|
||||
<div>
|
||||
<h2>Kursverlauf</h2>
|
||||
<p>Neueste Abrufe zuerst. Verlauf der bevorzugten Waehrungen relativ zur Anzeige-Basiswaehrung.</p>
|
||||
</div>
|
||||
<div class="fx-card-meta">
|
||||
<div><strong>Anzeige-Basis:</strong> <?= e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? '')) ?></div>
|
||||
<div><strong>Letzter Abruf:</strong> <?= e((string) ($latest['fetched_at_display'] ?? $latest['fetched_at'] ?? 'noch keiner')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fx-table-wrap">
|
||||
<table class="fx-table">
|
||||
<thead data-bind="history-head">
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Kurse</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="history-body">
|
||||
<tr><td colspan="2">Noch keine Verlaufsdaten geladen.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= module_shell_footer() ?>
|
||||
Reference in New Issue
Block a user