iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

commit fd2f848489b9448155d09f3e5413ac7af971d39f
parent c03617ca167e3800e8e6424f967d253a97ceb829
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Thu, 27 Aug 2026 23:14:56 +0200

Make management UI mobile friendly

Diffstat:
Msrc/adapters/http/index_html.rs | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 84 insertions(+), 29 deletions(-)

diff --git a/src/adapters/http/index_html.rs b/src/adapters/http/index_html.rs @@ -33,8 +33,9 @@ pub(super) const INDEX_HTML: &str = concat!( .nav-button svg.chain-icon { stroke-width: 1.35; } .nav-button span { font-size: 11px; font-weight: 800; } .nav-button:hover, .nav-button.active { background: #202328; border-color: #3b4448; color: #d5f55f; } - .settings-button { margin-top: auto; width: 64px; min-height: 54px; display: grid; place-items: center; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; color: #9fa8ad; background: transparent; text-align: center; } + .settings-button { margin-top: auto; width: 64px; min-height: 54px; display: grid; place-items: center; gap: 4px; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; color: #9fa8ad; background: transparent; text-align: center; } .settings-button svg { width: 23px; height: 23px; stroke: currentColor; stroke-width: 1.9; fill: none; } + .settings-button span { display: none; font-size: 10px; font-weight: 800; } .settings-button:hover, .settings-button.active { background: #202328; border-color: #3b4448; color: #d5f55f; } .version-panel { width: 64px; display: grid; gap: 4px; justify-items: center; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; color: #7f888e; background: transparent; font-size: 10px; font-weight: 850; text-align: center; } .version-panel.update { border-color: #566d25; color: #d5f55f; background: #1c2516; cursor: pointer; } @@ -89,7 +90,7 @@ pub(super) const INDEX_HTML: &str = concat!( th, td { text-align: left; border-bottom: 1px solid #2a3035; padding: 8px; vertical-align: top; } th { color: #8d989f; font-size: 11px; text-transform: uppercase; } code { overflow-wrap: anywhere; color: #c7f5ea; } - .table-wrap { overflow-x: auto; } + .table-wrap { max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; } .muted { color: #8d989f; } .flash { position: fixed; top: 18px; right: 18px; z-index: 80; width: min(420px, calc(100vw - 36px)); border-radius: 6px; padding: 10px 12px; border: 1px solid; font-weight: 700; box-shadow: 0 18px 48px rgba(0, 0, 0, .38); } .flash.success { color: #d5f55f; background: #1c2516; border-color: #566d25; } @@ -460,28 +461,81 @@ pub(super) const INDEX_HTML: &str = concat!( @media (max-width: 760px) { .utxo-flow, .mine-action-row, .mine-stats { grid-template-columns: 1fr; } .utxo-arrow { min-height: 28px; transform: rotate(90deg); } .tx-modal-head { align-items: stretch; } } @media (max-width: 920px) { .setup-grid, .wallet-grid, .mining-grid, .detail-grid, .network-health { grid-template-columns: 1fr; } } @media (max-width: 760px) { - .app-shell { display: grid; grid-template-columns: 1fr; } - .sidebar { position: sticky; inset: auto; width: auto; height: auto; flex-direction: row; justify-content: space-between; padding: 8px; border-right: 0; border-bottom: 1px solid #262b2f; } - .brand-mark { width: 34px; height: 34px; } - .brand-mark svg { width: 22px; height: 22px; } - .side-nav { display: flex; width: auto; gap: 8px; } - .nav-button { width: 52px; min-height: 48px; } + body { padding-bottom: calc(68px + env(safe-area-inset-bottom)); } + .app-shell { display: block; min-height: 100dvh; overflow: visible; } + .sidebar { position: fixed; z-index: 20; inset: auto 0 0; width: 100%; height: auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(48px, 1fr)); gap: 4px; padding: 6px max(8px, env(safe-area-inset-right)) calc(6px + env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left)); border: 0; border-top: 1px solid #30363b; background: rgba(21, 23, 26, .96); box-shadow: 0 -12px 32px rgba(0, 0, 0, .28); backdrop-filter: blur(14px); } + .brand-mark, .version-panel { display: none; } + .side-nav { display: contents; } + .nav-button { width: 100%; min-width: 0; min-height: 50px; gap: 2px; padding: 5px 2px; } .nav-button span { font-size: 10px; } - .settings-button, .version-panel { margin-top: 0; width: 48px; min-height: 48px; padding: 6px 3px; } - .settings-button svg { width: 21px; height: 21px; } - .content { padding: 16px 12px 36px; } + .settings-button { margin-top: 0; width: 100%; min-width: 0; min-height: 50px; gap: 2px; padding: 5px 2px; } + .settings-button svg { width: 20px; height: 20px; } + .settings-button span { display: block; } + .content { padding: 14px 12px 24px; } header, .split, .setup-grid, .wallet-grid, .mining-grid, .detail-grid, .wallet-tx-row { grid-template-columns: 1fr; } - header { display: grid; } - .settings-mode-row { align-items: flex-start; } + header { display: flex; align-items: flex-start; padding-bottom: 14px; } + h1 { font-size: 24px; } + h2 { font-size: 17px; } + .header-actions { flex-direction: column-reverse; gap: 6px; align-items: flex-end; text-align: right; font-size: 12px; } + .basic-status-row { flex-wrap: wrap; } + .page-title { margin-bottom: 12px; } + .panel, .metric { padding: 12px; } + .panel-head { align-items: flex-start; } + .settings-mode-row { flex-direction: column; align-items: stretch; } + .settings-mode-row .segmented { width: 100%; } + .settings-mode-row .segmented button { flex: 1; } .metrics-head { align-items: flex-start; flex-direction: column; } .metrics-range { width: 100%; } .metrics-range button { flex: 1 1 0; } .segmented.setup-mode-picker { grid-template-columns: 1fr; } .metrics-grid { grid-template-columns: 1fr; } - input { min-width: 0; width: 100%; } + form { width: 100%; align-items: stretch; } + form > label, .burn-fields, .mine-fee-fields, .recipient-field, .amount-field, .send-utxo-summary { width: 100%; } + .burn-fields, .mine-fee-fields { display: grid; grid-template-columns: 1fr; align-items: stretch; } + input, textarea { min-width: 0; width: 100%; font-size: 16px; } .switch input { width: auto; } + form > button.primary, .burn-fields > button.primary, .setup-actions > button.primary { min-height: 44px; } + .peer-toolbar { display: grid; } + .peer-form { display: grid; grid-template-columns: 1fr; } + .peer-form label { min-width: 0; } + .peer-form button { min-height: 44px; } + .peer-table, .peer-table tbody, .peer-table tr, .peer-table td { display: block; width: 100%; } + .peer-table { margin: 0; } + .peer-table thead { display: none; } + .peer-table tbody { display: grid; gap: 10px; } + .peer-table tr { overflow: hidden; border: 1px solid #2f363c; border-radius: 8px; background: #111316; } + .peer-table td { display: grid; grid-template-columns: minmax(82px, .38fr) minmax(0, 1fr); gap: 10px; align-items: baseline; border-bottom: 1px solid #2a3035; padding: 9px 10px; overflow-wrap: anywhere; } + .peer-table td::before { content: attr(data-label); color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; } + .peer-table td:last-child { border-bottom: 0; } + .peer-table td[colspan] { display: block; } + .peer-table td[colspan]::before { display: none; } + .peer-actions { justify-content: flex-start; } .seed-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .block-card { flex-basis: 108px; } + .setup-overlay { place-items: end center; padding: 8px; } + .setup-modal, .tx-modal { width: 100%; max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px); border-radius: 12px; padding: 14px; } + .setup-actions, .danger-actions, .address-book-modal-actions { flex-wrap: wrap; } + .setup-actions button, .danger-actions button, .address-book-modal-actions button { min-height: 44px; } + .tx-modal-head { flex-direction: column; } + .tx-modal-head > button { width: 100%; min-height: 44px; } + .flash { top: 10px; right: 10px; width: calc(100vw - 20px); } + .wallet-tx-list, .tx-scroll-list { max-height: none; overflow-y: visible; padding-right: 0; } + .mining-event { grid-template-columns: auto minmax(0, 1fr); } + .mining-event-time { grid-column: 2; } + } + @media (max-width: 420px) { + .content { padding-inline: 9px; } + .sidebar { gap: 2px; padding-inline: 4px; } + .nav-button, .settings-button { min-height: 48px; } + .nav-button svg, .settings-button svg { width: 19px; height: 19px; } + .nav-button span, .settings-button span { font-size: 9px; } + .panel, .metric, .block-rail-wrap { border-radius: 7px; padding: 10px; } + .grid, .peer-summary, .network-health-grid, .metrics-summary, .tx-modal-summary { grid-template-columns: 1fr 1fr; } + .wallet-balance-line { width: 100%; justify-content: space-between; } + .wallet-tx-main { padding-right: 0; padding-top: 24px; } + .wallet-tx-row .pill { top: 9px; left: 9px; right: auto; } + .tx-field, .detail-kv, .stratum-field { grid-template-columns: 1fr; gap: 3px; } + .seed-grid { grid-template-columns: 1fr; } } </style> <script defer src="/assets/iuna-ui.js?v="#, @@ -517,6 +571,7 @@ pub(super) const INDEX_HTML: &str = concat!( </nav> <button class="settings-button" :class="{ active: tab === 'settings' }" type="button" @click="setTab('settings')" title="Settings" aria-label="Settings"> <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.7 3.2 9.2 5.5a7.2 7.2 0 0 0-1.4.8L5.6 5.6 3.2 9.8l1.7 1.6a7.8 7.8 0 0 0 0 1.6l-1.7 1.6 2.4 4.2 2.2-.7a7.2 7.2 0 0 0 1.4.8l.5 2.3h4.8l.5-2.3a7.2 7.2 0 0 0 1.4-.8l2.2.7 2.4-4.2-1.7-1.6a7.8 7.8 0 0 0 0-1.6L21 9.8l-2.4-4.2-2.2.7a7.2 7.2 0 0 0-1.4-.8l-.5-2.3H9.7Z"></path><circle cx="12" cy="12.2" r="3.1"></circle></svg> + <span>Settings</span> </button> <button class="version-panel" type="button" :class="{ update: updateAvailable(), checking: releaseCheckState === 'checking', failed: releaseCheckState === 'failed' }" :title="versionPanelTitle()" @click="openLatestRelease"> <span class="version-dot" aria-hidden="true"></span> @@ -859,25 +914,25 @@ pub(super) const INDEX_HTML: &str = concat!( <div class="peer-summary-item"><div class="peer-summary-label">Shared Height</div><div class="peer-summary-value" x-text="sharedHeightLabel()"></div></div> </div> <div class="table-wrap"> - <table> + <table class="peer-table"> <thead><tr><th>Status</th><th>Address</th><th>Direction</th><th>Last Contact</th><th x-show="developmentMode()">Clock</th><th x-show="developmentMode()">Ban</th><th x-show="developmentMode()">Score</th><th>Height</th><th x-show="developmentMode()">Delta</th><th x-show="developmentMode()">Tip</th><th x-show="developmentMode()">Sent</th><th x-show="developmentMode()">Received</th><th x-show="developmentMode()">Last Error</th><th>Actions</th></tr></thead> <tbody> <template x-for="peer in peers" :key="peer.address"> <tr> - <td><span class="peer-status" :class="peerStatus(peer)" x-text="peerStatusLabel(peer)"></span></td> - <td><code x-text="peer.address"></code></td> - <td x-text="peer.direction"></td> - <td x-text="peerLastContactLabel(peer)"></td> - <td x-show="developmentMode()" x-text="peerClockLabel(peer)"></td> - <td x-show="developmentMode()" x-text="peerBanLabel(peer)"></td> - <td x-show="developmentMode()" x-text="peer.misbehavior_score ?? 0"></td> - <td x-text="peer.last_known_height ?? '-'"></td> - <td x-show="developmentMode()" x-text="peerHeightDelta(peer)"></td> - <td x-show="developmentMode()"><code x-text="short(peer.last_known_tip_hash)"></code></td> - <td x-show="developmentMode()" x-text="peer.messages_sent"></td> - <td x-show="developmentMode()" x-text="peer.messages_received"></td> - <td x-show="developmentMode()" x-text="peer.last_error || ''"></td> - <td><div class="peer-actions"><button class="peer-remove" type="button" x-show="canRemovePeer(peer)" @click="removePeer(peer)">Remove</button><span class="muted" x-show="!canRemovePeer(peer)">Observed</span></div></td> + <td data-label="Status"><span class="peer-status" :class="peerStatus(peer)" x-text="peerStatusLabel(peer)"></span></td> + <td data-label="Address"><code x-text="peer.address"></code></td> + <td data-label="Direction" x-text="peer.direction"></td> + <td data-label="Last contact" x-text="peerLastContactLabel(peer)"></td> + <td data-label="Clock" x-show="developmentMode()" x-text="peerClockLabel(peer)"></td> + <td data-label="Ban" x-show="developmentMode()" x-text="peerBanLabel(peer)"></td> + <td data-label="Score" x-show="developmentMode()" x-text="peer.misbehavior_score ?? 0"></td> + <td data-label="Height" x-text="peer.last_known_height ?? '-'"></td> + <td data-label="Delta" x-show="developmentMode()" x-text="peerHeightDelta(peer)"></td> + <td data-label="Tip" x-show="developmentMode()"><code x-text="short(peer.last_known_tip_hash)"></code></td> + <td data-label="Sent" x-show="developmentMode()" x-text="peer.messages_sent"></td> + <td data-label="Received" x-show="developmentMode()" x-text="peer.messages_received"></td> + <td data-label="Last error" x-show="developmentMode()" x-text="peer.last_error || ''"></td> + <td data-label="Actions"><div class="peer-actions"><button class="peer-remove" type="button" x-show="canRemovePeer(peer)" @click="removePeer(peer)">Remove</button><span class="muted" x-show="!canRemovePeer(peer)">Observed</span></div></td> </tr> </template> <tr class="skeleton-card" x-show="peerPage.loading" aria-hidden="true">