mining
All checks were successful
Deploy / deploy-staging (push) Successful in 34s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-08-20 21:07:38 +02:00
parent c8f2f1c1ae
commit c345ee71cf
5 changed files with 292 additions and 7 deletions

View File

@@ -543,6 +543,11 @@
daily_cost_currency: 'EUR',
report_currency: 'EUR',
crypto_currency: 'DOGE',
crypto_base_price_amount: '5.49',
crypto_base_price_currency: 'USD',
min_offer_runtime_months: 24,
target_offer_hashrate_kh: 50,
target_offer_runtime_months: 36,
preferred_currencies: ['DOGE', 'USD', 'EUR'],
cost_plans: [],
currencies: [],
@@ -1049,6 +1054,11 @@
baseline_coins_total: '',
report_currency: 'EUR',
crypto_currency: 'DOGE',
crypto_base_price_amount: '5.49',
crypto_base_price_currency: 'USD',
min_offer_runtime_months: '24',
target_offer_hashrate_kh: '50',
target_offer_runtime_months: '36',
});
const [offerPreviewForm, setOfferPreviewForm] = useState({
crypto_base_price_amount: '',
@@ -1173,7 +1183,11 @@
const currentWalletWithdrawals = walletWithdrawalRows.length ? walletWithdrawalRows : (Array.isArray(currentSettings.wallet_withdrawals) ? currentSettings.wallet_withdrawals : []);
const currentWalletSnapshots = Array.isArray(payload?.wallet_snapshots) ? payload.wallet_snapshots : [];
const currentPurchasedMiners = Array.isArray(currentSettings.purchased_miners) ? currentSettings.purchased_miners : [];
const currentSummaryMinerOffers = Array.isArray(payload?.summary?.miner_offers) ? payload.summary.miner_offers : [];
const currentMiningCurrency = String((latest && latest.coin_currency) || currentSettings.crypto_currency || 'DOGE').toUpperCase();
const minOfferRuntimeMonths = Number(currentSettings.min_offer_runtime_months) > 0 ? Number(currentSettings.min_offer_runtime_months) : 24;
const targetOfferHashrateKh = Number(currentSettings.target_offer_hashrate_kh) > 0 ? Number(currentSettings.target_offer_hashrate_kh) : 50;
const targetOfferRuntimeMonths = Number(currentSettings.target_offer_runtime_months) > 0 ? Number(currentSettings.target_offer_runtime_months) : 36;
const overviewPerDayLabel = `${currentMiningCurrency} pro Tag`;
const latestMeasurementDate = latest && latest.measured_at ? parseStoredUtcDate(latest.measured_at) : null;
const latestMeasurementAgeMs = latestMeasurementDate ? (Date.now() - latestMeasurementDate.getTime()) : null;
@@ -1273,13 +1287,47 @@
return false;
}
if (Number.isFinite(runtimeMonths) && runtime !== runtimeMonths) {
if (Number.isFinite(runtimeMonths) && runtime < runtimeMonths) {
return false;
}
return true;
});
const scenarioMinerOffers = filteredMinerOffers;
const preferredMinerOffer = currentSummaryMinerOffers
.filter((offer) => String(offer.payment_type || '').toLowerCase() === 'crypto')
.map((offer) => {
const offerHashrateKh = Number(offer.offer_hashrate_mh) * 1000;
const runtime = Number(offer.runtime_months);
return {
...offer,
__runtimeDelta: Math.abs(runtime - targetOfferRuntimeMonths),
__hashrateDelta: Math.abs(offerHashrateKh - targetOfferHashrateKh),
};
})
.sort((left, right) => left.__runtimeDelta - right.__runtimeDelta || left.__hashrateDelta - right.__hashrateDelta)[0] || null;
const preferredOfferRequiredCoins = preferredMinerOffer
? convertCurrencyValue(
preferredMinerOffer.effective_price_amount ?? preferredMinerOffer.crypto_display_price_amount,
preferredMinerOffer.effective_price_currency || preferredMinerOffer.crypto_display_price_currency || currentMiningCurrency,
currentMiningCurrency
)
: null;
const preferredOfferCurrentCoins = latest ? Number(latest.coins_total_visible ?? latest.coins_total) : null;
const preferredOfferRemainingCoins = Number.isFinite(preferredOfferRequiredCoins) && Number.isFinite(preferredOfferCurrentCoins)
? Math.max(0, preferredOfferRequiredCoins - preferredOfferCurrentCoins)
: null;
const preferredOfferRemainingDays = preferredOfferRemainingCoins !== null
&& Number(latest?.doge_per_day_interval) > 0
? (preferredOfferRemainingCoins / Number(latest.doge_per_day_interval))
: null;
const preferredOfferEta = preferredOfferRemainingDays !== null && latest?.measured_at
? (
preferredOfferRemainingDays <= 0
? latest.measured_at
: new Date(parseStoredUtcDate(latest.measured_at).getTime() + (preferredOfferRemainingDays * 86400000)).toISOString()
)
: null;
const speedUnits = ['kH/s', 'MH/s'];
const fiatCurrencies = currencies.filter((currency) => !currency.is_crypto);
const cryptoCurrencies = currencies.filter((currency) => !!currency.is_crypto);
@@ -1791,7 +1839,31 @@
baseline_coins_total: normalized.settings.baseline_coins_total || '',
report_currency: normalized.settings.report_currency || 'EUR',
crypto_currency: normalized.settings.crypto_currency || 'DOGE',
crypto_base_price_amount: normalized.settings.crypto_base_price_amount !== null && normalized.settings.crypto_base_price_amount !== undefined
? String(normalized.settings.crypto_base_price_amount)
: '5.49',
crypto_base_price_currency: normalized.settings.crypto_base_price_currency || 'USD',
min_offer_runtime_months: normalized.settings.min_offer_runtime_months !== null && normalized.settings.min_offer_runtime_months !== undefined
? String(normalized.settings.min_offer_runtime_months)
: '24',
target_offer_hashrate_kh: normalized.settings.target_offer_hashrate_kh !== null && normalized.settings.target_offer_hashrate_kh !== undefined
? String(normalized.settings.target_offer_hashrate_kh)
: '50',
target_offer_runtime_months: normalized.settings.target_offer_runtime_months !== null && normalized.settings.target_offer_runtime_months !== undefined
? String(normalized.settings.target_offer_runtime_months)
: '36',
});
setOfferPreviewForm((current) => ({
crypto_base_price_amount: normalized.settings.crypto_base_price_amount !== null && normalized.settings.crypto_base_price_amount !== undefined
? String(normalized.settings.crypto_base_price_amount)
: (current.crypto_base_price_amount || '5.49'),
crypto_base_price_currency: normalized.settings.crypto_base_price_currency || current.crypto_base_price_currency || 'USD',
fiat_base_price_amount: current.fiat_base_price_amount || '',
}));
setMinerOfferFilters((previous) => ({
...previous,
runtime_months: previous.runtime_months || String(normalized.settings.min_offer_runtime_months ?? 24),
}));
setFxSelection(Array.isArray(normalized.settings.preferred_currencies) && normalized.settings.preferred_currencies.length
? normalized.settings.preferred_currencies
: ['DOGE', 'USD', 'EUR']);
@@ -2296,6 +2368,11 @@
daily_cost_currency: currentSettings.daily_cost_currency,
report_currency: settingsForm.report_currency || 'EUR',
crypto_currency: settingsForm.crypto_currency || 'DOGE',
crypto_base_price_amount: settingsForm.crypto_base_price_amount || '5.49',
crypto_base_price_currency: settingsForm.crypto_base_price_currency || 'USD',
min_offer_runtime_months: Number(settingsForm.min_offer_runtime_months) || 24,
target_offer_hashrate_kh: Number(settingsForm.target_offer_hashrate_kh) || 50,
target_offer_runtime_months: Number(settingsForm.target_offer_runtime_months) || 36,
preferred_currencies: Array.isArray(currentSettings.preferred_currencies)
? currentSettings.preferred_currencies
: fxSelection,
@@ -3223,6 +3300,21 @@
yLabel: `Kurs ${reportCurrency}`,
})),
]),
preferredMinerOffer ? h(StatCard, {
key: 'preferred-offer-target',
label: `Zielminer ${fmtNumber(targetOfferHashrateKh, 0)} kH/s · ${targetOfferRuntimeMonths} Monate`,
value: preferredOfferRequiredCoins !== null ? `${fmtNumber(preferredOfferRequiredCoins, 4)} ${currentMiningCurrency}` : 'n/a',
sub: [
preferredMinerOffer.usd_display_price_amount !== null ? `${fmtMoney(preferredMinerOffer.usd_display_price_amount, 'USD')}` : null,
preferredOfferRemainingDays !== null
? `Rest ${fmtNumber(preferredOfferRemainingDays, 2)} Tage`
: 'Resttage n/a',
preferredOfferRemainingCoins !== null
? `Rest-${currentMiningCurrency} ${fmtNumber(preferredOfferRemainingCoins, 4)}`
: null,
preferredOfferEta ? `ETA ${fmtDate(preferredOfferEta)}` : null,
].filter(Boolean).join(' · '),
}) : null,
panel('Zielmonitor', `Rest-${currentCoinCurrency} und Resttage werden gegen den letzten verfuegbaren Kurs je Zielwaehrung berechnet.`,
h('div', { className: 'mc-target-grid' },
currentTargets.map((target, index) => h('div', {
@@ -3642,9 +3734,9 @@
]),
panel('Miner-Angebote', selectedOfferType === 'crypto'
? (currentWalletMiningBalance !== null
? `Crypto-Angebote sind aktiv. Angezeigt werden nur Angebote, die dein NC-Wallet aktuell tragen kann (${fmtNumber(currentWalletMiningBalance, 8)} ${currentMiningCurrency} Gegenwert).`
: 'Crypto-Angebote sind aktiv. Solange kein Wallet-Bestand erkannt wird, bleiben die Krypto-Angebote sichtbar.')
: 'Fiat-Angebote sind aktiv. Die Berechnung startet erst, wenn du eine Geschwindigkeit eingibst.',
? `Crypto-Angebote sind aktiv. Standardmäßig werden Laufzeiten ab ${fmtNumber(minOfferRuntimeMonths, 0)} Monaten berücksichtigt und nur Angebote gezeigt, die dein NC-Wallet aktuell tragen kann (${fmtNumber(currentWalletMiningBalance, 8)} ${currentMiningCurrency} Gegenwert).`
: `Crypto-Angebote sind aktiv. Standardmäßig werden Laufzeiten ab ${fmtNumber(minOfferRuntimeMonths, 0)} Monaten berücksichtigt. Solange kein Wallet-Bestand erkannt wird, bleiben die Krypto-Angebote sichtbar.`)
: `Fiat-Angebote sind aktiv. Standardmäßig werden Laufzeiten ab ${fmtNumber(minOfferRuntimeMonths, 0)} Monaten berücksichtigt. Die Berechnung startet erst, wenn du eine Geschwindigkeit eingibst.`,
[
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
selectedOfferType === 'crypto'
@@ -3688,9 +3780,9 @@
? inputField('Max. DOGE', 'number', minerOfferFilters.max_doge, (value) => setMinerOfferFilters({ ...minerOfferFilters, max_doge: value }), '0.0001')
: null,
inputField(`Max. Basispreis (${reportCurrency})`, 'number', minerOfferFilters.price_max, (value) => setMinerOfferFilters({ ...minerOfferFilters, price_max: value }), '0.0001'),
selectField('Laufzeit', minerOfferFilters.runtime_months, [{ value: '', label: 'Alle Laufzeiten' }].concat(Array.from(new Set(evaluatedMinerOffers.map((offer) => String(offer.runtime_months || '')).filter(Boolean))).sort((a, b) => Number(a) - Number(b)).map((value) => ({
selectField('Min. Laufzeit', minerOfferFilters.runtime_months, [{ value: '', label: 'Alle Laufzeiten' }].concat(Array.from(new Set(evaluatedMinerOffers.map((offer) => String(offer.runtime_months || '')).filter(Boolean))).sort((a, b) => Number(a) - Number(b)).map((value) => ({
value,
label: `${value} Monate`,
label: `ab ${value} Monate`,
}))), (value) => setMinerOfferFilters({ ...minerOfferFilters, runtime_months: value })),
]),
visibleOfferBasisHistory.length ? h('div', { key: 'basis-history', className: 'mc-mini-grid' }, visibleOfferBasisHistory.map((entry) => h('div', {
@@ -4138,7 +4230,7 @@
]),
]),
h('div', { className: 'mc-stack' }, [
panel('Basis-Settings', 'Baseline bleibt als Referenzwert mit Datum und Uhrzeit bestehen.', h('form', {
panel('Basis-Settings', 'Baseline bleibt als Referenzwert mit Datum und Uhrzeit bestehen. Angebotsbasis und Zielminer steuern die automatische Miner-Kalkulation auf der Übersicht.', h('form', {
className: 'mc-form',
onSubmit: submitSettings,
}, [
@@ -4146,6 +4238,11 @@
inputField('Baseline Coins', 'number', settingsForm.baseline_coins_total, (value) => setSettingsForm({ ...settingsForm, baseline_coins_total: value }), '0.000001'),
selectField('Standard-FIAT-Währung', settingsForm.report_currency || 'EUR', selectableFiatCurrencies.map((currency) => currency.code), (value) => setSettingsForm({ ...settingsForm, report_currency: value })),
selectField('Standard-Krypto-Währung', settingsForm.crypto_currency || 'DOGE', selectableCryptoCurrencies.map((currency) => currency.code), (value) => setSettingsForm({ ...settingsForm, crypto_currency: value })),
inputField('Krypto-Basispreis 50 kH/s · 3 Monate', 'number', settingsForm.crypto_base_price_amount, (value) => setSettingsForm({ ...settingsForm, crypto_base_price_amount: value }), '0.000001'),
selectField('Basiswaehrung Krypto', settingsForm.crypto_base_price_currency || 'USD', Array.from(new Set(['USD', String(settingsForm.crypto_currency || 'DOGE').toUpperCase()])), (value) => setSettingsForm({ ...settingsForm, crypto_base_price_currency: value || 'USD' })),
inputField('Min. Mietlaufzeit in Monaten', 'number', settingsForm.min_offer_runtime_months, (value) => setSettingsForm({ ...settingsForm, min_offer_runtime_months: value }), '1'),
inputField('Zielminer Hashrate in kH/s', 'number', settingsForm.target_offer_hashrate_kh, (value) => setSettingsForm({ ...settingsForm, target_offer_hashrate_kh: value }), '1'),
inputField('Zielminer Laufzeit in Monaten', 'number', settingsForm.target_offer_runtime_months, (value) => setSettingsForm({ ...settingsForm, target_offer_runtime_months: value }), '1'),
h('button', {
type: 'submit',
className: 'mc-button mc-button--primary',

View File

@@ -112,3 +112,15 @@ Pro Abruf entsteht genau ein Datensatz in `fx-rates` mit Basiswaehrung, Provider
Falls noch historische Mining-Checker-Fetches in `miningcheck_fx_fetches` und `miningcheck_fx_rates` liegen, kann `POST /api/mining-checker/v1/projects/{projectKey}/legacy-fx-migrate` diese nach `fx-rates` ueberfuehren. Danach werden bestehende Messpunkte soweit moeglich auf die passende `fx_fetch_id` aktualisiert.
Fuer Auswertungen, Berichte und Listen speichert der Mining-Checker pro Messpunkt die damals passende `fx_fetch_id`. Historische Umrechnungen laufen damit gegen genau den zugeordneten `fx-rates`-Snapshot.
## Miner-Basis und Zielminer
Die Angebotsberechnung fuer Krypto-Miner nutzt eine konfigurierbare Basis fuer `50 kH/s` und `3 Monate`. Standard ist `5.49 USD`, kann aber in den Modul-Settings angepasst werden.
Zusaetzlich koennen in den Settings diese Leitwerte hinterlegt werden:
- minimale gewuenschte Mietlaufzeit in Monaten
- Zielminer-Hashrate in `kH/s`
- Zielminer-Laufzeit in Monaten
Die Uebersicht berechnet daraus automatisch den theoretischen Preis des Zielminers, den benoetigten Krypto-Gegenwert und die Resttage bis zur theoretischen Anmietung auf Basis des letzten Uploads.