conversions web4

This commit is contained in:
Filipriec
2026-08-24 10:36:13 +02:00
parent fbe05feed2
commit 6784969081
11 changed files with 92 additions and 227 deletions

View File

@@ -358,11 +358,8 @@ ecb-description = Importér načítá zveřejněné kurzy ECB jednou denně a za
ecb-unavailable-title = Devizové kurzy nejsou dostupné ecb-unavailable-title = Devizové kurzy nejsou dostupné
ecb-forbidden = Pro otevření této stránky se vyžaduje čtení údajů o devizových kurzech. ecb-forbidden = Pro otevření této stránky se vyžaduje čtení údajů o devizových kurzech.
ecb-import-attempts = Pokusy o import ecb-import-attempts = Pokusy o import
ecb-failed-count = { $count -> ecb-technical-history = Technická historie aktualizací
[one] { $count } neúspěšný ecb-technical-history-hint = Pouze pro řešení problémů. Stará selhání zůstávají v auditním záznamu i po úspěšnější aktualizaci.
[few] { $count } neúspěšné
*[other] { $count } neúspěšných
}
ecb-no-attempts-hint = Importér nezaznamenal žádný pokus. Běží při startu a poté denně, takže prázdný záznam znamená, že server od vytvoření této tabulky nedokončil žádný běh. ecb-no-attempts-hint = Importér nezaznamenal žádný pokus. Běží při startu a poté denně, takže prázdný záznam znamená, že server od vytvoření této tabulky nedokončil žádný běh.
ecb-th-result = Výsledek ecb-th-result = Výsledek
ecb-th-started = Spuštěno ecb-th-started = Spuštěno
@@ -376,11 +373,11 @@ ecb-batch-running = probíhá
ecb-batch-failed = neúspěšné ecb-batch-failed = neúspěšné
ecb-still-running = stále běží ecb-still-running = stále běží
ecb-audit-hint = Pokusy jsou auditní záznam: nelze je upravovat ani mazat a dávka, která znovu načetla den, který už vlastnila starší, nevloží nic — proto může být nový počet nižší než celkový. ecb-audit-hint = Pokusy jsou auditní záznam: nelze je upravovat ani mazat a dávka, která znovu načetla den, který už vlastnila starší, nevloží nic — proto může být nový počet nižší než celkový.
ecb-import-running = import probíhá ecb-import-running = právě se aktualizuje
ecb-postable-lead = Co lze zaúčtovat právě teď ecb-postable-lead = Povolená data transakcí
ecb-ordinary-transactions = Běžné transakce ecb-ordinary-transactions = Běžné transakce
ecb-statements-title = Výpisy a rozhodující data ecb-statements-title = Výpisy a zůstatky k datu
ecb-dated-up-to = datované do včetně ecb-dated-up-to = Do
ecb-ordinary-rate-hint = Přepočteno kurzem zveřejněným v pracovní den před transakcí. ecb-ordinary-rate-hint = Přepočteno kurzem zveřejněným v pracovní den před transakcí.
ecb-statements-rate-hint = Přepočteno kurzem zveřejněným v ten den, nebo v poslední pracovní den před ním. ecb-statements-rate-hint = Přepočteno kurzem zveřejněným v ten den, nebo v poslední pracovní den před ním.
ecb-postable-limit-hint = Cizoměnová částka datovaná po svém limitu se odmítá přímo, dokud importér nedoběhne — nepřepočítává se zastaralým kurzem. ecb-postable-limit-hint = Cizoměnová částka datovaná po svém limitu se odmítá přímo, dokud importér nedoběhne — nepřepočítává se zastaralým kurzem.
@@ -398,18 +395,20 @@ ecb-no-coverage = bez pokrytí
ecb-should-reach = Má dosáhnout ecb-should-reach = Má dosáhnout
ecb-last-attempt = Poslední pokus ecb-last-attempt = Poslední pokus
ecb-none-recorded = žádný nezaznamenán ecb-none-recorded = žádný nezaznamenán
ecb-last-success = Poslední úspěch ecb-last-success = Poslední úspěšná aktualizace
ecb-none = žádný ecb-none = žádný
ecb-next-import = Další import ecb-next-import = Další automatická aktualizace
ecb-ready-currencies = Připravené měny:
ecb-latest-problem = Poslední problém:
ecb-currencies-on = Měny k { $date } ecb-currencies-on = Měny k { $date }
ecb-the-covered-day = pokrytý den ecb-the-covered-day = pokrytý den
ecb-most-recent-failure = Poslední neúspěch — dávka { $batch }, spuštěno { $started }: ecb-most-recent-failure = Poslední neúspěch — dávka { $batch }, spuštěno { $started }:
ecb-no-reason-recorded = nebyl zaznamenán žádný důvod. ecb-no-reason-recorded = nebyl zaznamenán žádný důvod.
ecb-headline-current = Kurzy jsou aktuální ecb-headline-current = Účtování v cizí měně je dostupné
ecb-headline-never-ran = Nikdy nebyly importovány žádné kurzy ecb-headline-never-ran = Účtování v cizí měně není dostupné
ecb-headline-behind = Kurzy se zpožďují ecb-headline-behind = Některé cizoměnové záznamy mohou být zablokovány
ecb-explanation-current = Ověřen je každý publikační den až po nejnovější, takže konverze se zaúčtovávají normálně. ecb-explanation-current = Potřebné kurzy jsou dostupné. Správce nemusí nic dělat.
ecb-explanation-never-ran = Dokud se jeden nepodaří, každá konverze v cizí měně se odmítá pro nedostatek pokrytí. ecb-explanation-never-ran = Zatím neuspěla žádná aktualizace kurzů. Cizoměnové záznamy nelze zaúčtovat.
ecb-explanation-behind = { $count -> ecb-explanation-behind = { $count ->
[one] Chybí { $count } publikační den. [one] Chybí { $count } publikační den.
[few] Chybí { $count } publikační dny. [few] Chybí { $count } publikační dny.
@@ -934,8 +933,8 @@ rates-tab-configuration = Konfigurace
rates-tab-configuration-sub = Měny a poskytovatelé rates-tab-configuration-sub = Měny a poskytovatelé
rates-tab-evidence = Důkazy rates-tab-evidence = Důkazy
rates-tab-evidence-sub = Kontrola uloženého výsledku rates-tab-evidence-sub = Kontrola uloženého výsledku
rates-tab-providers = Poskytovatelé rates-tab-providers = Dostupnost
rates-tab-providers-sub = Pokrytí a importy rates-tab-providers-sub = Stav a aktualizace
rates-profile-configuration = Konfigurace profilu rates-profile-configuration = Konfigurace profilu
rates-no-profiles = Nejsou dostupné žádné účetní profily. rates-no-profiles = Nejsou dostupné žádné účetní profily.
rates-profile = Profil rates-profile = Profil
@@ -970,9 +969,8 @@ rates-revision = Revize řádku
rates-conversion = Konverze rates-conversion = Konverze
rates-created = Zaznamenáno rates-created = Zaznamenáno
rates-more-evidence = Existují další důkazy; upřesněte vyhledávání nebo pokračujte kurzorem API. rates-more-evidence = Existují další důkazy; upřesněte vyhledávání nebo pokračujte kurzorem API.
rates-provider-operations = Provoz rates-provider-health = Lze účtovat cizoměnové záznamy?
rates-provider-health = Stav poskytovatele rates-provider-health-hint = Aktuální dostupnost a poslední úspěšná automatická aktualizace.
rates-provider-health-hint = Ověřené pokrytí, hranice účtování a nedávné pokusy o import oficiálního poskytovatele.
rates-unavailable-title = Konverzní kurzy nejsou dostupné rates-unavailable-title = Konverzní kurzy nejsou dostupné
rates-err-profile-missing = Vybraný profil již neexistuje. Zobrazuje se první dostupný profil. rates-err-profile-missing = Vybraný profil již neexistuje. Zobrazuje se první dostupný profil.
rates-err-record-id = ID záznamu musí být kladné celé číslo. rates-err-record-id = ID záznamu musí být kladné celé číslo.

View File

@@ -354,10 +354,8 @@ ecb-description = The importer fetches the ECB's published rates once a day and
ecb-unavailable-title = Exchange rates unavailable ecb-unavailable-title = Exchange rates unavailable
ecb-forbidden = Reading exchange-rate data is required to open this page. ecb-forbidden = Reading exchange-rate data is required to open this page.
ecb-import-attempts = Import attempts ecb-import-attempts = Import attempts
ecb-failed-count = { $count -> ecb-technical-history = Technical update history
[one] { $count } failed ecb-technical-history-hint = For troubleshooting only. Old failures remain in this audit log even after a later update succeeds.
*[other] { $count } failed
}
ecb-no-attempts-hint = The importer has not recorded an attempt. It runs on startup and then daily, so an empty log means the server has not completed a run since this table was created. ecb-no-attempts-hint = The importer has not recorded an attempt. It runs on startup and then daily, so an empty log means the server has not completed a run since this table was created.
ecb-th-result = Result ecb-th-result = Result
ecb-th-started = Started ecb-th-started = Started
@@ -371,11 +369,11 @@ ecb-batch-running = running
ecb-batch-failed = failed ecb-batch-failed = failed
ecb-still-running = still running ecb-still-running = still running
ecb-audit-hint = Attempts are an audit record: they cannot be edited or deleted, and a batch that re-fetched a day an earlier one already owned inserts nothing, which is why the new count can be lower than the total. ecb-audit-hint = Attempts are an audit record: they cannot be edited or deleted, and a batch that re-fetched a day an earlier one already owned inserts nothing, which is why the new count can be lower than the total.
ecb-import-running = import running ecb-import-running = updating now
ecb-postable-lead = What can be posted right now ecb-postable-lead = Accepted transaction dates
ecb-ordinary-transactions = Ordinary transactions ecb-ordinary-transactions = Regular transactions
ecb-statements-title = Statements & decisive dates ecb-statements-title = Statements and dated balances
ecb-dated-up-to = dated up to and including ecb-dated-up-to = Through
ecb-ordinary-rate-hint = Converted at the rate published the business day before the transaction. ecb-ordinary-rate-hint = Converted at the rate published the business day before the transaction.
ecb-statements-rate-hint = Converted at the rate published on that day, or the last business day before it. ecb-statements-rate-hint = Converted at the rate published on that day, or the last business day before it.
ecb-postable-limit-hint = A foreign-currency amount dated after its limit is refused outright until the importer catches up — it is not converted at a stale rate. ecb-postable-limit-hint = A foreign-currency amount dated after its limit is refused outright until the importer catches up — it is not converted at a stale rate.
@@ -392,18 +390,20 @@ ecb-no-coverage = no coverage
ecb-should-reach = Should reach ecb-should-reach = Should reach
ecb-last-attempt = Last attempt ecb-last-attempt = Last attempt
ecb-none-recorded = none recorded ecb-none-recorded = none recorded
ecb-last-success = Last success ecb-last-success = Last successful update
ecb-none = none ecb-none = none
ecb-next-import = Next import ecb-next-import = Next automatic update
ecb-ready-currencies = Currencies ready:
ecb-latest-problem = Latest problem:
ecb-currencies-on = Currencies on { $date } ecb-currencies-on = Currencies on { $date }
ecb-the-covered-day = the covered day ecb-the-covered-day = the covered day
ecb-most-recent-failure = Most recent failure — batch { $batch }, started { $started }: ecb-most-recent-failure = Most recent failure — batch { $batch }, started { $started }:
ecb-no-reason-recorded = no reason was recorded. ecb-no-reason-recorded = no reason was recorded.
ecb-headline-current = Rates are current ecb-headline-current = Foreign-currency posting is available
ecb-headline-never-ran = No rates have ever been imported ecb-headline-never-ran = Foreign-currency posting is unavailable
ecb-headline-behind = Rates are behind ecb-headline-behind = Some foreign-currency records may be blocked
ecb-explanation-current = Every publication day up to the latest one is verified, so conversions post normally. ecb-explanation-current = The required rates are available. No admin action is needed.
ecb-explanation-never-ran = Until one succeeds, every conversion in a foreign currency is refused for want of coverage. ecb-explanation-never-ran = No rate update has succeeded yet. Foreign-currency records cannot be posted.
ecb-explanation-behind = { $count -> ecb-explanation-behind = { $count ->
[one] { $count } publication day is missing. [one] { $count } publication day is missing.
*[other] { $count } publication days are missing. *[other] { $count } publication days are missing.
@@ -918,8 +918,8 @@ rates-tab-configuration = Configuration
rates-tab-configuration-sub = Currencies and providers rates-tab-configuration-sub = Currencies and providers
rates-tab-evidence = Evidence rates-tab-evidence = Evidence
rates-tab-evidence-sub = Inspect a stored result rates-tab-evidence-sub = Inspect a stored result
rates-tab-providers = Providers rates-tab-providers = Availability
rates-tab-providers-sub = Coverage and imports rates-tab-providers-sub = Status and updates
rates-profile-configuration = Profile configuration rates-profile-configuration = Profile configuration
rates-no-profiles = No accounting profiles are available. rates-no-profiles = No accounting profiles are available.
rates-profile = Profile rates-profile = Profile
@@ -954,9 +954,8 @@ rates-revision = Row revision
rates-conversion = Conversion rates-conversion = Conversion
rates-created = Recorded at rates-created = Recorded at
rates-more-evidence = More evidence exists; narrow the lookup or use the API cursor to continue. rates-more-evidence = More evidence exists; narrow the lookup or use the API cursor to continue.
rates-provider-operations = Operations rates-provider-health = Can foreign-currency records be posted?
rates-provider-health = Provider health rates-provider-health-hint = Current availability and the latest successful automatic update.
rates-provider-health-hint = Verified coverage, posting horizons, and recent import attempts for the official provider.
rates-unavailable-title = Conversion rates are unavailable rates-unavailable-title = Conversion rates are unavailable
rates-err-profile-missing = The selected profile no longer exists. The first available profile is shown instead. rates-err-profile-missing = The selected profile no longer exists. The first available profile is shown instead.
rates-err-record-id = Record ID must be a positive whole number. rates-err-record-id = Record ID must be a positive whole number.

View File

@@ -358,11 +358,8 @@ ecb-description = Importér načítava zverejnené kurzy ECB raz denne a zazname
ecb-unavailable-title = Devízové kurzy nie sú dostupné ecb-unavailable-title = Devízové kurzy nie sú dostupné
ecb-forbidden = Na otvorenie tejto stránky sa vyžaduje čítanie údajov o devízových kurzoch. ecb-forbidden = Na otvorenie tejto stránky sa vyžaduje čítanie údajov o devízových kurzoch.
ecb-import-attempts = Pokusy o import ecb-import-attempts = Pokusy o import
ecb-failed-count = { $count -> ecb-technical-history = Technická história aktualizácií
[one] { $count } zlyhaný ecb-technical-history-hint = Len na riešenie problémov. Staré zlyhania zostávajú v audítorskom zázname aj po úspešnejšej aktualizácii.
[few] { $count } zlyhané
*[other] { $count } zlyhaných
}
ecb-no-attempts-hint = Importér nezaznamenal žiadny pokus. Beží pri štarte a potom denne, takže prázdny záznam znamená, že server od vytvorenia tejto tabuľky nedokončil žiadny beh. ecb-no-attempts-hint = Importér nezaznamenal žiadny pokus. Beží pri štarte a potom denne, takže prázdny záznam znamená, že server od vytvorenia tejto tabuľky nedokončil žiadny beh.
ecb-th-result = Výsledok ecb-th-result = Výsledok
ecb-th-started = Spustené ecb-th-started = Spustené
@@ -376,11 +373,11 @@ ecb-batch-running = prebieha
ecb-batch-failed = zlyhané ecb-batch-failed = zlyhané
ecb-still-running = stále beží ecb-still-running = stále beží
ecb-audit-hint = Pokusy sú audítorský záznam: nemožno ich upravovať ani mazať a dávka, ktorá znovu načítala deň, ktorý už vlastnila staršia, nevloží nič — preto môže byť nový počet nižší ako celkový. ecb-audit-hint = Pokusy sú audítorský záznam: nemožno ich upravovať ani mazať a dávka, ktorá znovu načítala deň, ktorý už vlastnila staršia, nevloží nič — preto môže byť nový počet nižší ako celkový.
ecb-import-running = import prebieha ecb-import-running = práve sa aktualizuje
ecb-postable-lead = Čo možno zahlasovať práve teraz ecb-postable-lead = Povolené dátumy transakcií
ecb-ordinary-transactions = Bežné transakcie ecb-ordinary-transactions = Bežné transakcie
ecb-statements-title = Výpisy a rozhodujúce dátumy ecb-statements-title = Výpisy a zostatky k dátumu
ecb-dated-up-to = datované do vrátane ecb-dated-up-to = Do
ecb-ordinary-rate-hint = Prepočítané kurzom zverejneným v pracovný deň pred transakciou. ecb-ordinary-rate-hint = Prepočítané kurzom zverejneným v pracovný deň pred transakciou.
ecb-statements-rate-hint = Prepočítané kurzom zverejneným v ten deň, alebo v posledný pracovný deň pred ním. ecb-statements-rate-hint = Prepočítané kurzom zverejneným v ten deň, alebo v posledný pracovný deň pred ním.
ecb-postable-limit-hint = Cudzomenová suma datovaná po svojom limite sa odmieta priamo, kým importér nedobehne — neprepočítava sa zastaraným kurzom. ecb-postable-limit-hint = Cudzomenová suma datovaná po svojom limite sa odmieta priamo, kým importér nedobehne — neprepočítava sa zastaraným kurzom.
@@ -398,18 +395,20 @@ ecb-no-coverage = bez pokrytia
ecb-should-reach = Má dosiahnuť ecb-should-reach = Má dosiahnuť
ecb-last-attempt = Posledný pokus ecb-last-attempt = Posledný pokus
ecb-none-recorded = žiadny nezaznamenaný ecb-none-recorded = žiadny nezaznamenaný
ecb-last-success = Posledný úspech ecb-last-success = Posledná úspešná aktualizácia
ecb-none = žiadny ecb-none = žiadny
ecb-next-import = Ďalší import ecb-next-import = Ďalšia automatická aktualizácia
ecb-ready-currencies = Pripravené meny:
ecb-latest-problem = Posledný problém:
ecb-currencies-on = Meny k { $date } ecb-currencies-on = Meny k { $date }
ecb-the-covered-day = pokrytý deň ecb-the-covered-day = pokrytý deň
ecb-most-recent-failure = Posledné zlyhanie — dávka { $batch }, spustené { $started }: ecb-most-recent-failure = Posledné zlyhanie — dávka { $batch }, spustené { $started }:
ecb-no-reason-recorded = nebol zaznamenaný žiadny dôvod. ecb-no-reason-recorded = nebol zaznamenaný žiadny dôvod.
ecb-headline-current = Kurzy sú aktuálne ecb-headline-current = Účtovanie v cudzej mene je dostupné
ecb-headline-never-ran = Nikdy neboli importované žiadne kurzy ecb-headline-never-ran = Účtovanie v cudzej mene nie je dostupné
ecb-headline-behind = Kurzy meškajú ecb-headline-behind = Niektoré cudzomenové záznamy môžu byť zablokované
ecb-explanation-current = Overený je každý publikačný deň až po najnovší, takže konverzie sa hlasujú normálne. ecb-explanation-current = Potrebné kurzy sú dostupné. Správca nemusí nič robiť.
ecb-explanation-never-ran = Kým sa jeden nepodarí, každá konverzia v cudzej mene sa odmieta pre nedostatok pokrytia. ecb-explanation-never-ran = Zatiaľ neuspela žiadna aktualizácia kurzov. Cudzomenové záznamy nemožno zaúčtovať.
ecb-explanation-behind = { $count -> ecb-explanation-behind = { $count ->
[one] Chýba { $count } publikačný deň. [one] Chýba { $count } publikačný deň.
[few] Chýbajú { $count } publikačné dni. [few] Chýbajú { $count } publikačné dni.
@@ -932,8 +931,8 @@ rates-tab-configuration = Konfigurácia
rates-tab-configuration-sub = Meny a poskytovatelia rates-tab-configuration-sub = Meny a poskytovatelia
rates-tab-evidence = Dôkazy rates-tab-evidence = Dôkazy
rates-tab-evidence-sub = Kontrola uloženého výsledku rates-tab-evidence-sub = Kontrola uloženého výsledku
rates-tab-providers = Poskytovatelia rates-tab-providers = Dostupnosť
rates-tab-providers-sub = Pokrytie a importy rates-tab-providers-sub = Stav a aktualizácie
rates-profile-configuration = Konfigurácia profilu rates-profile-configuration = Konfigurácia profilu
rates-no-profiles = Nie sú dostupné žiadne účtovné profily. rates-no-profiles = Nie sú dostupné žiadne účtovné profily.
rates-profile = Profil rates-profile = Profil
@@ -968,9 +967,8 @@ rates-revision = Revízia riadka
rates-conversion = Konverzia rates-conversion = Konverzia
rates-created = Zaznamenané rates-created = Zaznamenané
rates-more-evidence = Existujú ďalšie dôkazy; spresnite vyhľadávanie alebo pokračujte kurzorom API. rates-more-evidence = Existujú ďalšie dôkazy; spresnite vyhľadávanie alebo pokračujte kurzorom API.
rates-provider-operations = Prevádzka rates-provider-health = Možno účtovať cudzomenové záznamy?
rates-provider-health = Stav poskytovateľa rates-provider-health-hint = Aktuálna dostupnosť a posledná úspešná automatická aktualizácia.
rates-provider-health-hint = Overené pokrytie, hranice účtovania a nedávne pokusy o import oficiálneho poskytovateľa.
rates-unavailable-title = Konverzné kurzy nie sú dostupné rates-unavailable-title = Konverzné kurzy nie sú dostupné
rates-err-profile-missing = Vybraný profil už neexistuje. Zobrazuje sa prvý dostupný profil. rates-err-profile-missing = Vybraný profil už neexistuje. Zobrazuje sa prvý dostupný profil.
rates-err-record-id = ID záznamu musí byť kladné celé číslo. rates-err-record-id = ID záznamu musí byť kladné celé číslo.

View File

@@ -61,7 +61,6 @@ pub(crate) async fn load_ecb_page(
Ok(EcbPageState { Ok(EcbPageState {
nav: crate::ui::Nav::from_authorization(headers, "ecb", &authorization), nav: crate::ui::Nav::from_authorization(headers, "ecb", &authorization),
verified_through_date: status.verified_through_date, verified_through_date: status.verified_through_date,
latest_verifiable_date: status.latest_verifiable_date,
healthy: status.healthy, healthy: status.healthy,
days_behind: status.days_behind, days_behind: status.days_behind,
import_running: status.import_running, import_running: status.import_running,

View File

@@ -147,7 +147,6 @@ impl ImportBatchView {
pub(crate) struct EcbPageState { pub(crate) struct EcbPageState {
pub nav: crate::ui::Nav, pub nav: crate::ui::Nav,
pub verified_through_date: Option<String>, pub verified_through_date: Option<String>,
pub latest_verifiable_date: String,
pub healthy: bool, pub healthy: bool,
pub days_behind: i32, pub days_behind: i32,
pub import_running: bool, pub import_running: bool,
@@ -188,24 +187,6 @@ impl EcbPageState {
} }
} }
/// The "3 publication days behind" tag in the coverage row.
pub(crate) fn behind_label(&self) -> String {
tr!(
self.nav.locale,
"ecb-behind-count",
"count" => self.days_behind as i64,
)
}
/// "2 failed" above the attempts table.
pub(crate) fn failed_count_label(&self) -> String {
tr!(
self.nav.locale,
"ecb-failed-count",
"count" => self.failure_count() as i64,
)
}
/// The class the status card is painted with. /// The class the status card is painted with.
pub(crate) fn health_class(&self) -> &'static str { pub(crate) fn health_class(&self) -> &'static str {
if self.healthy { "healthy" } else { "unhealthy" } if self.healthy { "healthy" } else { "unhealthy" }
@@ -230,17 +211,6 @@ impl EcbPageState {
} }
} }
/// The newest attempt of any outcome, which is what "has the importer run
/// at all lately" asks about.
pub(crate) fn last_attempt(&self) -> Option<String> {
let batch = self.batches.first()?;
let when = batch.completed_at.as_deref().unwrap_or(&batch.started_at);
Some(match from_now(self.nav.locale, when) {
Some(relative) => format!("{} ({relative})", moment(when)),
None => moment(when),
})
}
/// Successes are what move coverage; a run of failures on top of an old /// Successes are what move coverage; a run of failures on top of an old
/// success is a different picture from no runs at all. /// success is a different picture from no runs at all.
pub(crate) fn last_success(&self) -> Option<String> { pub(crate) fn last_success(&self) -> Option<String> {
@@ -252,13 +222,6 @@ impl EcbPageState {
}) })
} }
/// Failures among the attempts on the page. One is an incident; several in
/// a row is a broken pipeline, and the count is the only thing that
/// distinguishes them at a glance.
pub(crate) fn failure_count(&self) -> usize {
self.batches.iter().filter(|batch| batch.failed()).count()
}
/// Alphabetical, because the reader is looking one up rather than reading /// Alphabetical, because the reader is looking one up rather than reading
/// the list through. /// the list through.
pub(crate) fn currencies(&self) -> Vec<String> { pub(crate) fn currencies(&self) -> Vec<String> {

View File

@@ -83,7 +83,6 @@ mod tests {
EcbPageState { EcbPageState {
nav: Nav::default(), nav: Nav::default(),
verified_through_date: Some("2026-08-12".to_string()), verified_through_date: Some("2026-08-12".to_string()),
latest_verifiable_date: "2026-08-12".to_string(),
healthy: true, healthy: true,
days_behind: 0, days_behind: 0,
import_running: false, import_running: false,
@@ -102,7 +101,7 @@ mod tests {
let html = render_page(&healthy_page()); let html = render_page(&healthy_page());
assert!(!html.contains("Template error"), "{html}"); assert!(!html.contains("Template error"), "{html}");
assert!(html.contains("Rates are current")); assert!(html.contains("Foreign-currency posting is available"));
assert!(html.contains("healthy")); assert!(html.contains("healthy"));
assert!(html.contains("2026-08-12")); assert!(html.contains("2026-08-12"));
assert!(html.contains("CZK")); assert!(html.contains("CZK"));
@@ -120,10 +119,9 @@ mod tests {
let html = render_page(&page); let html = render_page(&page);
assert!(!html.contains("Template error"), "{html}"); assert!(!html.contains("Template error"), "{html}");
assert!(html.contains("Rates are behind")); assert!(html.contains("Some foreign-currency records may be blocked"));
assert!(html.contains("unhealthy")); assert!(html.contains("unhealthy"));
assert!(html.contains("3 publication days are missing")); assert!(html.contains("3 publication days are missing"));
assert!(html.contains("3 publication days behind"));
} }
/// Timestamps arrive as RFC 3339 instants and must not reach the page that /// Timestamps arrive as RFC 3339 instants and must not reach the page that
@@ -140,17 +138,19 @@ mod tests {
assert!(html.contains("4s")); assert!(html.contains("4s"));
} }
/// The counts above the table are how a run of bad days is told apart from /// Historical failures are troubleshooting detail, not a contradictory
/// one bad day without reading every row. /// headline beside a healthy current status.
#[test] #[test]
fn failed_attempts_are_counted_above_the_table() { fn historical_failures_are_collapsed_as_technical_history() {
let mut page = healthy_page(); let mut page = healthy_page();
page.batches.push(batch(8, "failed")); page.batches.push(batch(8, "failed"));
page.batches.push(batch(7, "failed")); page.batches.push(batch(7, "failed"));
let html = render_page(&page); let html = render_page(&page);
assert!(html.contains("2 failed"), "{html}"); assert!(html.contains("Technical update history"), "{html}");
assert!(html.contains("<details class=\"panel\">"), "{html}");
assert!(!html.contains("2 failed"), "{html}");
} }
/// Coverage is a publication date; the reader's question is which dates /// Coverage is a publication date; the reader's question is which dates
@@ -160,12 +160,12 @@ mod tests {
fn the_card_says_which_dates_can_be_posted() { fn the_card_says_which_dates_can_be_posted() {
let html = render_page(&healthy_page()); let html = render_page(&healthy_page());
assert!(html.contains("What can be posted right now"), "{html}"); assert!(html.contains("Accepted transaction dates"), "{html}");
assert!(html.contains("Ordinary transactions")); assert!(html.contains("Regular transactions"));
assert!(html.contains("<strong>2026-08-13</strong>"), "{html}"); assert!(html.contains("<strong>2026-08-13</strong>"), "{html}");
// Askama escapes `&` numerically. The title now comes from the // Askama escapes `&` numerically. The title now comes from the
// catalogue as plain text, where it was pre-escaped markup before. // catalogue as plain text, where it was pre-escaped markup before.
assert!(html.contains("Statements &#38; decisive dates"), "{html}"); assert!(html.contains("Statements and dated balances"), "{html}");
assert!(html.contains("<strong>2026-08-12</strong>"), "{html}"); assert!(html.contains("<strong>2026-08-12</strong>"), "{html}");
} }
@@ -184,8 +184,8 @@ mod tests {
let html = render_page(&page); let html = render_page(&page);
assert!(!html.contains("Template error"), "{html}"); assert!(!html.contains("Template error"), "{html}");
assert!(html.contains("No rates have ever been imported")); assert!(html.contains("Foreign-currency posting is unavailable"));
assert!(html.contains("every conversion in a foreign currency is refused")); assert!(html.contains("Foreign-currency records cannot be posted"));
// And the postable block says so too rather than showing an empty date. // And the postable block says so too rather than showing an empty date.
assert!(html.contains("<strong>Nothing.</strong>"), "{html}"); assert!(html.contains("<strong>Nothing.</strong>"), "{html}");
} }

View File

@@ -53,7 +53,6 @@ mod tests {
EcbPageState { EcbPageState {
nav: Nav::default(), nav: Nav::default(),
verified_through_date: Some("2026-08-23".to_string()), verified_through_date: Some("2026-08-23".to_string()),
latest_verifiable_date: "2026-08-23".to_string(),
healthy: true, healthy: true,
days_behind: 0, days_behind: 0,
import_running: false, import_running: false,
@@ -115,7 +114,10 @@ mod tests {
assert!(!html.contains("Template error"), "{html}"); assert!(!html.contains("Template error"), "{html}");
assert!(html.contains("Profile configuration"), "{html}"); assert!(html.contains("Profile configuration"), "{html}");
assert!(html.contains("Evidence lookup"), "{html}"); assert!(html.contains("Evidence lookup"), "{html}");
assert!(html.contains("Provider health"), "{html}"); assert!(
html.contains("Can foreign-currency records be posted?"),
"{html}"
);
assert!(html.contains("CZK"), "{html}"); assert!(html.contains("CZK"), "{html}");
assert!(html.contains("ecb"), "{html}"); assert!(html.contains("ecb"), "{html}");
assert!(html.contains("Ready"), "{html}"); assert!(html.contains("Ready"), "{html}");

View File

@@ -1,8 +1,6 @@
<section class="panel"> <details class="panel">
<h2> <summary><strong>{{ nav.tr("ecb-technical-history") }}</strong> <span class="count">{{ page.batches.len() }}</span></summary>
{{ nav.tr("ecb-import-attempts") }} <span class="count">{{ page.batches.len() }}</span> <p class="hint">{{ nav.tr("ecb-technical-history-hint") }}</p>
{%- if page.failure_count() > 0 %}<span class="tag tag-bad">{{ page.failed_count_label() }}</span>{% endif -%}
</h2>
{% if page.batches.is_empty() %} {% if page.batches.is_empty() %}
<p class="hint">{{ nav.tr("ecb-no-attempts-hint") }}</p> <p class="hint">{{ nav.tr("ecb-no-attempts-hint") }}</p>
{% else %} {% else %}
@@ -55,4 +53,4 @@
</div> </div>
<p class="hint">{{ nav.tr("ecb-audit-hint") }}</p> <p class="hint">{{ nav.tr("ecb-audit-hint") }}</p>
{% endif %} {% endif %}
</section> </details>

View File

@@ -18,63 +18,6 @@
{% include "pages/admin/ecb/status.html" %} {% include "pages/admin/ecb/status.html" %}
<section class="panel"> {% include "pages/admin/ecb/attempts.html" %}
<h2>
{{ nav.tr("ecb-import-attempts") }} <span class="count">{{ page.batches.len() }}</span>
{%- if page.failure_count() > 0 %}<span class="tag tag-bad">{{ page.failed_count_label() }}</span>{% endif -%}
</h2>
{% if page.batches.is_empty() %}
<p class="hint">{{ nav.tr("ecb-no-attempts-hint") }}</p>
{% else %}
<div class="table-scroll">
<table class="builder-table">
<thead>
<tr>
<th class="mark">#</th><th>{{ nav.tr("ecb-th-result") }}</th><th>{{ nav.tr("ecb-th-started") }}</th><th>{{ nav.tr("ecb-th-finished") }}</th>
<th>{{ nav.tr("ecb-th-took") }}</th><th>{{ nav.tr("ecb-th-window") }}</th><th class="numeric">{{ nav.tr("ecb-th-observations") }}</th>
<th>{{ nav.tr("ecb-th-verified-through") }}</th>
</tr>
</thead>
<tbody>
{% for batch in page.batches %}
<tr{% if batch.failed() %} class="attempt-failed"{% endif %}>
<td class="mark">{{ batch.batch_id }}</td>
<td>
{%- if batch.succeeded() %}<span class="tag tag-ok">{{ nav.tr("ecb-batch-succeeded") }}</span>
{%- else if batch.running() %}<span class="tag">{{ nav.tr("ecb-batch-running") }}</span>
{%- else %}<span class="tag tag-bad">{{ nav.tr("ecb-batch-failed") }}</span>{% endif -%}
</td>
<td class="moment">{{ batch.started() }}</td>
<td class="moment">
{%- if let Some(finished) = batch.finished() %}{{ finished }}
{%- else %}<span class="hint">{{ nav.tr("ecb-still-running") }}</span>{% endif -%}
</td>
<td class="numeric">
{%- if let Some(took) = batch.took(nav.locale) %}{{ took }}
{%- else %}<span class="hint"></span>{% endif -%}
</td>
<td>
{{ batch.window() }}
<div class="hint endpoint" title="{{ batch.endpoint }}"><code>{{ batch.endpoint }}</code></div>
</td>
<td class="numeric">{{ batch.observations(nav.locale) }}</td>
<td class="moment">
{%- if let Some(date) = batch.verified_through_date %}{{ date }}
{%- else %}<span class="hint"></span>{% endif -%}
</td>
</tr>
{% if let Some(message) = batch.error_message %}
<tr class="attempt-error">
<td class="mark"></td>
<td colspan="7">{{ message }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<p class="hint">{{ nav.tr("ecb-audit-hint") }}</p>
{% endif %}
</section>
</main> </main>
{% endblock %} {% endblock %}

View File

@@ -16,10 +16,6 @@
</h2> </h2>
<p class="hint">{{ page.explanation() }}</p> <p class="hint">{{ page.explanation() }}</p>
{# The question a reader came with. Coverage is a publication date, and
turning it into "which transaction dates will post" means applying the
rule the backend applies -- so the backend applies it and sends both
bounds, rather than the page inviting the reader to do it in their head. #}
<div class="postable"> <div class="postable">
<p class="postable-lead">{{ nav.tr("ecb-postable-lead") }}</p> <p class="postable-lead">{{ nav.tr("ecb-postable-lead") }}</p>
{% if let Some(through) = page.transactions_postable_through %} {% if let Some(through) = page.transactions_postable_through %}
@@ -27,48 +23,21 @@
<div> <div>
<dt>{{ nav.tr("ecb-ordinary-transactions") }}</dt> <dt>{{ nav.tr("ecb-ordinary-transactions") }}</dt>
<dd>{{ nav.tr("ecb-dated-up-to") }} <strong>{{ through }}</strong></dd> <dd>{{ nav.tr("ecb-dated-up-to") }} <strong>{{ through }}</strong></dd>
<dd class="hint">{{ nav.tr("ecb-ordinary-rate-hint") }}</dd>
</div> </div>
<div> <div>
<dt>{{ nav.tr("ecb-statements-title") }}</dt> <dt>{{ nav.tr("ecb-statements-title") }}</dt>
<dd>{{ nav.tr("ecb-dated-up-to") }} <strong>{{ page.statements_postable_through.as_deref().unwrap_or("—") }}</strong></dd> <dd>{{ nav.tr("ecb-dated-up-to") }} <strong>{{ page.statements_postable_through.as_deref().unwrap_or("—") }}</strong></dd>
<dd class="hint">{{ nav.tr("ecb-statements-rate-hint") }}</dd>
</div> </div>
</dl> </dl>
<p class="hint">{{ nav.tr("ecb-postable-limit-hint") }}</p>
{% else %} {% else %}
<p> <p><strong>{{ nav.tr("ecb-nothing") }}</strong> {{ nav.tr("ecb-no-publication-hint") }}</p>
<strong>{{ nav.tr("ecb-nothing") }}</strong> {{ nav.tr("ecb-no-publication-hint") }}
</p>
{% endif %} {% endif %}
</div> </div>
{# Coverage first and on its own line: the two dates and the gap between
them are the whole answer, and burying them in a four-column grid made
them read as trivia. #}
<div class="coverage">
<div class="coverage-point">
<span class="coverage-label">{{ nav.tr("ecb-verified-through") }}</span>
<span class="coverage-date">
{%- if let Some(date) = page.verified_through_date %}{{ date }}
{%- else %}{{ nav.tr("ecb-never") }}{% endif -%}
</span>
</div>
<div class="coverage-gap">
{%- if page.healthy %}<span class="tag tag-ok">{{ nav.tr("ecb-up-to-date") }}</span>
{%- else if page.days_behind > 0 %}<span class="tag tag-bad">{{ page.behind_label() }}</span>
{%- else %}<span class="tag tag-bad">{{ nav.tr("ecb-no-coverage") }}</span>{% endif -%}
</div>
<div class="coverage-point">
<span class="coverage-label">{{ nav.tr("ecb-should-reach") }}</span>
<span class="coverage-date">{{ page.latest_verifiable_date }}</span>
</div>
</div>
<dl class="status-grid"> <dl class="status-grid">
<div> <div>
<dt>{{ nav.tr("ecb-last-attempt") }}</dt> <dt>{{ nav.tr("ecb-verified-through") }}</dt>
<dd>{% if let Some(when) = page.last_attempt() %}{{ when }}{% else %}<span class="hint">{{ nav.tr("ecb-none-recorded") }}</span>{% endif %}</dd> <dd>{% if let Some(date) = page.verified_through_date %}<strong>{{ date }}</strong>{% else %}<span class="hint">{{ nav.tr("ecb-never") }}</span>{% endif %}</dd>
</div> </div>
<div> <div>
<dt>{{ nav.tr("ecb-last-success") }}</dt> <dt>{{ nav.tr("ecb-last-success") }}</dt>
@@ -78,25 +47,21 @@
<dt>{{ nav.tr("ecb-next-import") }}</dt> <dt>{{ nav.tr("ecb-next-import") }}</dt>
<dd>{{ page.next_import() }}</dd> <dd>{{ page.next_import() }}</dd>
</div> </div>
<div>
<dt>{% if let Some(date) = page.verified_through_date %}{{ nav.tr_args("ecb-currencies-on", [("date", date.clone())]) }}{% else %}{{ nav.tr("ecb-the-covered-day") }}{% endif %}</dt>
<dd>
{%- if page.covered_currencies.is_empty() %}<span class="hint">{{ nav.tr("ecb-none") }}</span>
{%- else %}{{ page.covered_currencies.len() }}{% endif -%}
</dd>
</div>
</dl> </dl>
{% if !page.covered_currencies.is_empty() %} {% if !page.covered_currencies.is_empty() %}
<div class="currency-list"> <div class="currency-list">
<strong>{{ nav.tr("ecb-ready-currencies") }}</strong>
{% for currency in page.currencies() %}<span class="tag">{{ currency }}</span>{% endfor %} {% for currency in page.currencies() %}<span class="tag">{{ currency }}</span>{% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if let Some(failure) = page.last_failure() %} {% if !page.healthy %}
<p class="hint failure-note"> {% if let Some(failure) = page.last_failure() %}
{{ nav.tr_args("ecb-most-recent-failure", [("batch", failure.batch_id.to_string()), ("started", failure.started())]) }} <p class="failure-note">
{% if let Some(message) = failure.error_message %}{{ message }}{% else %}{{ nav.tr("ecb-no-reason-recorded") }}{% endif %} <strong>{{ nav.tr("ecb-latest-problem") }}</strong>
</p> {% if let Some(message) = failure.error_message %}{{ message }}{% else %}{{ nav.tr("ecb-no-reason-recorded") }}{% endif %}
</p>
{% endif %}
{% endif %} {% endif %}
</section> </section>

View File

@@ -142,7 +142,7 @@
</section> </section>
<section id="providers"> <section id="providers">
<div class="heading"><div><p class="eyebrow">{{ nav.tr("rates-provider-operations") }}</p><h2>{{ nav.tr("rates-provider-health") }}</h2><p>{{ nav.tr("rates-provider-health-hint") }}</p></div></div> <div class="heading"><div><h2>{{ nav.tr("rates-provider-health") }}</h2><p>{{ nav.tr("rates-provider-health-hint") }}</p></div></div>
{{ provider_status|safe }} {{ provider_status|safe }}
{{ provider_attempts|safe }} {{ provider_attempts|safe }}
</section> </section>