dashboard: nama akun jadi link https://vexascan.com/account/{owner} (V51) — index list (4 spot) + history pay-list + live search, .owner-link target=_blank ; image web rebuild + push
This commit is contained in:
1 parent
5618bad427
commit
9b040a8c71
7 files changed
+25
-10
No files matched your search
@@ -44,7 +44,7 @@ Two tools scan the Vexanium blockchain voters table for accounts whose only vote
|
||||
- `claim.py` — klaim reward BP harian: baca `last_claim_time` (tabel `producers`, di-retry V46) → `next_window` = +24 jam, math UTC-naive via `_utcnow()` (V49/B13 — ⊥ `datetime.now()` lokal yang geser +TZ → poll prematur) → (≥24 jam lewat → clamp ke now, recovery B8); tidur sampai mendekat, lalu poll `vexcore::claimrewards` (sign pyntelope `VEX_BP_PRIVATE_KEY`) tiap `CLAIM_RETRY_SECONDS`; deteksi mendarat via `_confirm_landed` di KEDUA jalur (send sukses & exception) = Hyperion `executed` ATAU `last_claim_time` maju dari baseline (V46/B11 — `send()` pyntelope ⊥ raise saat chain menolak, HTTP 500 sbg body; Hyperion `executed: false` utk tx baru-mendarat-belum-terindeks juga dicek lewat `_claim_time_advanced`, V50/B14); tak terkonfirmasi → retry senyap; reward = isi tx via Hyperion (bpay+vpay → BP, `reward_from_tx`) prioritas, selisih saldo fallback; reward ⊥ terukur → fee `pending` + notif `KLAIM MENDARAT · REWARD TAK TERUKUR` (V33/B10); fee 10% (`VEX_BP_FEE_PERCENT`) floor 4-des → `VEX_BP_FEE_WALLET` via `distribute.build_signed_transfer`, memo `BP FEE YYYY-MM-DD`; parse `get_table_rows` pakai `data.get('rows')` (bentuk asli dict, ⊥ `data[0]` — B7/V39); `step()` = state machine (resume fee → jadwal → poll), source of truth `claim_runs` (fee pending/failed diulang + resume saat restart). Modul logika (⊥ CLI). `test_claim.py` — oracle (mock chain+pyntelope, bentuk respons asli, temp DB).
|
||||
- `claim_loop.py` — scheduler harian dalam container stack (service `claim`): `_wait_db` (duplikat scan_loop) lalu `claim.step()` terus, loop tak pernah keluar.
|
||||
- `telegram.py` — kirim notifikasi status distribusi + klaim via Telegram Bot API (best-effort; ⊥ token/chat id → kanal no-op senyap; gagal kirim → log saja). Dua kanal: `TELEGRAM_COMMUNITY_CHAT_IDS` (CSV) = brief mulai/selesai distribusi saja; `TELEGRAM_INTERNAL_CHAT_IDS` (CSV) = detail distribusi + semua kegagalan + klaim reward (⊥ komunitas, V30/V36). `send_text(text, chat_ids=None)` = komunitas, `send_internal(text)` = internal; dari env/`.env`/NFS `config.env`.
|
||||
- `dashboard.py` + `templates/index.html` + `templates/history.html` + `static/style.css` + `static/app.js` — Flask web dashboard; reads the store via `db.py`, styled per `DESIGN.md`; `app.js` = debounced live owner search (fetch `/api/search`), degrades to the server-side `?q=` GET form if JS is off. Three vote-age bands (V40): the voters list is split into BARU (top, `PEMILIH BARU · BELUM MATANG`) and the main list (`voter-list` section, `.reward` cells, V29) where KADALUARSA rows are pinned at the top with amber styling + `VOTE ULANG` badge + legend before the paged VALID rows, which LEFT-joins a `status='sent'` payments aggregate for the TOTAL REWARD column; since V42 the VALID list also holds REVOTE (returning re-voters, `voter_first_seen.first_seen_at ≤ now−3d`) tagged with a mint `REVOTE` tag (legend `TAG MINT = REVOTE KURANG DARI 1 HARI · VALID LANGSUNG`, shows only while `last_vote > now−VEX_REVOTE_TAG_DAYS`) — no separate band, no maturity wait; only genuinely new voters sit in BARU tagged `BARU`; since V43 VALID rows within `VEX_WARN_DAYS` of the KADALUARSA cutoff get an amber dashed `VOTE ULANG SEGERA` badge + legend `TAG AMBER = KADALUARSA DALAM 3 HARI · N PEMILIH` (display-only); search (`?q=` and `/api/search`) covers all bands and tags each result (`group` = `baru|revote|valid|kadaluarsa` + `expired` + `expiring`). Banner BERITA (V38) shows the next distribution run from `DISTRIBUTE_HOUR`/`DISTRIBUTE_WEEKDAY`/`DISTRIBUTE_FIRST_RUN` + `TZ` (mirror of `distribute_loop.next_boundary`, ⊥ impor lintas-image; drift guarded in test_dashboard); the `SETIAP ... · HH:00` recurrence line renders only for regular weekly rounds — hidden while the next event is the one-off first-run (`_is_first_run_next`, date ≠ weekly day). A `stats-note` caption states the list is single-vote-to-BP accounts ≥ min stake (V47). `/history` = riwayat distribusi read-only; `run-list` = six-column desktop/tablet grid, `pay-list` = four-column grid (AKUN/JUMLAH/STATUS/TXID) showing only the latest run with its date in the title, and labeled mobile cards.
|
||||
- `dashboard.py` + `templates/index.html` + `templates/history.html` + `static/style.css` + `static/app.js` — Flask web dashboard; reads the store via `db.py`, styled per `DESIGN.md`; `app.js` = debounced live owner search (fetch `/api/search`), degrades to the server-side `?q=` GET form if JS is off. Three vote-age bands (V40): the voters list is split into BARU (top, `PEMILIH BARU · BELUM MATANG`) and the main list (`voter-list` section, `.reward` cells, V29) where KADALUARSA rows are pinned at the top with amber styling + `VOTE ULANG` badge + legend before the paged VALID rows, which LEFT-joins a `status='sent'` payments aggregate for the TOTAL REWARD column; since V42 the VALID list also holds REVOTE (returning re-voters, `voter_first_seen.first_seen_at ≤ now−3d`) tagged with a mint `REVOTE` tag (legend `TAG MINT = REVOTE KURANG DARI 1 HARI · VALID LANGSUNG`, shows only while `last_vote > now−VEX_REVOTE_TAG_DAYS`) — no separate band, no maturity wait; only genuinely new voters sit in BARU tagged `BARU`; since V43 VALID rows within `VEX_WARN_DAYS` of the KADALUARSA cutoff get an amber dashed `VOTE ULANG SEGERA` badge + legend `TAG AMBER = KADALUARSA DALAM 3 HARI · N PEMILIH` (display-only); search (`?q=` and `/api/search`) covers all bands and tags each result (`group` = `baru|revote|valid|kadaluarsa` + `expired` + `expiring`). Banner BERITA (V38) shows the next distribution run from `DISTRIBUTE_HOUR`/`DISTRIBUTE_WEEKDAY`/`DISTRIBUTE_FIRST_RUN` + `TZ` (mirror of `distribute_loop.next_boundary`, ⊥ impor lintas-image; drift guarded in test_dashboard); the `SETIAP ... · HH:00` recurrence line renders only for regular weekly rounds — hidden while the next event is the one-off first-run (`_is_first_run_next`, date ≠ weekly day). A `stats-note` caption states the list is single-vote-to-BP accounts ≥ min stake (V47); account names link to `https://vexascan.com/account/{owner}` in the index list, `/history` pay-list, and live search (`.owner-link`, V51). `/history` = riwayat distribusi read-only; `run-list` = six-column desktop/tablet grid, `pay-list` = four-column grid (AKUN/JUMLAH/STATUS/TXID) showing only the latest run with its date in the title, and labeled mobile cards.
|
||||
- `gunicorn.conf.py` — gunicorn production config (bind/workers from `config`, sync worker). `run.sh` — launcher: `./run.sh` = `./venv/bin/gunicorn -c gunicorn.conf.py dashboard:app` from any cwd.
|
||||
- `config.py` — loads env/`.env` (python-dotenv) → `TARGET_BP`, `API_NODE`, `DB_PATH`, `DB_BACKEND`, `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASS`, `DB_NAME`, `MIN_STAKED_VEX`, `PAGE_SIZE`, `DASH_HOST`, `DASH_PORT`, `DASH_WORKERS`, `VEX_STALE_DAYS`, `VEX_MATURITY_DAYS`, `VEX_EXPIRED_DAYS`, `DATABISNIS_API`, `DASH_LIQUID_TTL`, `BP_PRIVATE_KEY`, `DISTRIBUTE_HOUR`, `DISTRIBUTE_MAX_ATTEMPTS`, `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_IDS`, `CLAIM_RETRY_SECONDS`, `BP_FEE_WALLET`, `BP_FEE_PERCENT`; shared by get_voters & dashboard.
|
||||
- `scan_loop.py` — scheduler dalam container stack: menunggu batas `SCAN_HOURS` (lokal via `TZ`), panggil `get_voters.main()`; skan-awal `SCAN_RUN_ON_START` + tunggu DB siap; loop tak pernah keluar.
|
||||
|
||||
@@ -88,7 +88,7 @@ V10: dashboard baca `voters.db` → rows sort `staked` DESC (terbesar dulu); ⊥
|
||||
V11: search `q` → filter owner substring (LIKE, wildcard-escape, case-insensitive) di SEMUA band tampil (V40); urut+paging terjaga; `?q=` (daftar datar ber-tag) & `/api/search` read-only; tiap hasil membawa `group` (`baru`|`valid`|`kadaluarsa`) + `expired`; rank = ordinal DALAM kelompok (⊥ rank global lintas band, V40)
|
||||
V12: produksi web disajikan gunicorn (`dashboard:app`, `gunicorn.conf.py`); `DASH_WORKERS` default 2; `.env` ikut termuat via import `config`
|
||||
V13: `last_vote` = epoch 2000-01-01 + `round(52 × log2(last_vote_weight / (staked×10000)))` PEKAN (Vexanium/EOSIO `stake2vote`: eksponen = `int64((now − epoch)/(86400×7)) / 52.0` → bobot dikuantisasi pekan utuh; jadi tanggal kelipatan 7 hari, 00:00:00; regresi oracle baris nyata `..tg` 1291 pekan → 2024-09-28, `1.crownz` 1314 pekan → 2025-03-08); None bila bobot/stake nol; estimasi yang melewati `now` (unstake tanpa revote) diklamp ke `now`; voter dgn `last_vote=None` dibuang di `normalize` (bobot tak bisa diverifikasi → tanggal tak diketahui, ⊥ bisa diklasifikasi); V40: vote basi (> VEX_STALE_DAYS) TIDAK lagi dibuang di scan — SEMUA umur disimpan; banding/tampil/bayar menghitung umur saat query (⊥ peran scan)
|
||||
V15: web list = kolom RANK/AKUN/STAKE (VEX)/TOTAL REWARD (VEX)/VOTE TERAKHIR — ⊥ BOBOT SUARA; stats = SALDO LIQUID/TOTAL PEMILIH/STAKE TERTINGGI atas semua baris TAMPIL (V40: ⊖ yang tersembunyi > 31 hari); TOTAL VEX dihapus (V48: raw single-vote stake mudah disalahartikan); V40/V42/V43: BARU (`PEMILIH BARU · BELUM MATANG`, new saja) + daftar utama — kadaluarsa menyatu DI ATAS baris VALID (dipin, sorot amber + badge `VOTE ULANG` + legenda `BARIS AMBER = VOTE LEWAT …`), lalu VALID (pager) = matang-baru + REVOTE (tag mint `REVOTE` + legenda `TAG MINT = REVOTE KURANG DARI 1 HARI · VALID LANGSUNG`, V43) + badge amber dashed `VOTE ULANG SEGERA` (V43) pada baris `exp_cut < last_vote ≤ warn_cut` + legenda `TAG AMBER = KADALUARSA DALAM {WARN} HARI · N PEMILIH`; REVOTE ⊖ lagi bagian sendiri (V42); `/api/search` hasil `{owner,staked,weight,rank,last_vote,total_reward,group,expired,expiring}` — `group` = `baru|revote|valid|kadaluarsa` (weight disimpan & di-API, ⊥ dirender); V47: catatan `stats-note` menyatakan TOTAL PEMILIH = akun suara tunggal ke BP stake ≥ `MIN_STAKED_VEX`.
|
||||
V15: web list = kolom RANK/AKUN/STAKE (VEX)/TOTAL REWARD (VEX)/VOTE TERAKHIR — ⊥ BOBOT SUARA; stats = SALDO LIQUID/TOTAL PEMILIH/STAKE TERTINGGI atas semua baris TAMPIL (V40: ⊖ yang tersembunyi > 31 hari); TOTAL VEX dihapus (V48: raw single-vote stake mudah disalahartikan); V40/V42/V43: BARU (`PEMILIH BARU · BELUM MATANG`, new saja) + daftar utama — kadaluarsa menyatu DI ATAS baris VALID (dipin, sorot amber + badge `VOTE ULANG` + legenda `BARIS AMBER = VOTE LEWAT …`), lalu VALID (pager) = matang-baru + REVOTE (tag mint `REVOTE` + legenda `TAG MINT = REVOTE KURANG DARI 1 HARI · VALID LANGSUNG`, V43) + badge amber dashed `VOTE ULANG SEGERA` (V43) pada baris `exp_cut < last_vote ≤ warn_cut` + legenda `TAG AMBER = KADALUARSA DALAM {WARN} HARI · N PEMILIH`; REVOTE ⊖ lagi bagian sendiri (V42); `/api/search` hasil `{owner,staked,weight,rank,last_vote,total_reward,group,expired,expiring}` — `group` = `baru|revote|valid|kadaluarsa` (weight disimpan & di-API, ⊥ dirender); V47: catatan `stats-note` menyatakan TOTAL PEMILIH = akun suara tunggal ke BP stake ≥ `MIN_STAKED_VEX`; V51: nama akun (index + history pay-list + search live) jadi link `https://vexascan.com/account/{owner}` (`.owner-link`, `target=_blank rel=noopener`) — display-only
|
||||
V16: saldo liquid akun `TARGET_BP` dari `GET {DATABISNIS_API}/v2/state/get_account` (parse `account.core_liquid_balance`) → sel stat SALDO LIQUID; cache in-memory per worker TTL `DASH_LIQUID_TTL`; gagal fetch → nilai lama (atau `—` bila belum pernah sukses) + `at` ikut diset (cooldown, ⊥ pukulan berulang); read-only, ⊥ tulis DB
|
||||
V17: penyimpanan lewat `db.py` (backend `VEX_DB_BACKEND` = `sqlite` default | `mysql`); query berbagi sintaks pakai placeholder `%s` (diterjemahkan `?` utk sqlite); LIKE escape pakai `ESCAPE '!'` — backslash memutus literal string MySQL (B2); `db.query` → list (fetchall sqlite=list / PyMySQL=tuple diseragamkan, B3); replace snapshot mysql = CREATE IF NOT EXISTS + DELETE + INSERT satu transaksi (MVCC → pembaca dapat snapshot konsisten saat ganti harian, tak kena torn read); oracle utama (test_get_voters/test_dashboard) tetap sqlite hermetik; `test_mariadb.py` opt-in (skip exit 0 bila `VEX_DB_BACKEND != mysql`)
|
||||
V18: stack produksi = Docker Swarm (`docker-compose.yml`, `docker stack deploy -c docker-compose.yml databisnisid`): mariadb bind mount `/data/db/mariadb/databisnisid/data` pd node `server5.saltis.id` + placement constraint `node.hostname == server5.saltis.id` (data node-lokal → mariadb harus selalu di node itu; di swarm yg ⊥ punya node itu task-nya Pending, bukan bug); web (gunicorn, publik `:5000`) + scan (`scan_loop.py`) + distribute (`distribute_loop.py`, image `databisnisid-dist`) di node mana pun, terhubung via overlay `appnet`; image dari registry `git.proit.id/proitlab/databisnisid-web` & `-scan` & `-dist` (⊥ `build:`); scan berjalan pd jam `SCAN_HOURS` (default 0,8,16, lokal `TZ` default Asia/Jakarta via `datetime.now()`) + skan-awal `SCAN_RUN_ON_START` (setelah DB siap) → tabel voters langsung ada (dashboard ⊥ 500); distribute loop harian `DISTRIBUTE_HOUR` (default 10, lokal) — ⊥ distribusi-awal saat start (snapshot bisa basi); key `VEX_BP_PRIVATE_KEY` dari bind `/mnt/nfs/server5.saltis.id/data/databisnisid/app/config.env:/app/.env:ro` dibaca `config.py` `load_dotenv` (⊥ interpolasi env compose); loop tak pernah keluar; `stack deploy` ⊥ `env_file` (env inline via `${VAR}` interpolasi `.env`); image web set `DASH_HOST=0.0.0.0` agar ingress menjangkau; healthcheck web = cek socket TCP (⊥ ketergantungan isi DB)
|
||||
|
||||
+3
-1
@@ -91,7 +91,9 @@
|
||||
'<span class="warn-badge">VOTE ULANG SEGERA</span>' : '';
|
||||
return '<div class="list-row' + (r.expired ? ' expired' : '') + '">' +
|
||||
'<span class="rank">' + r.rank + '</span>' +
|
||||
'<span class="owner">' + escapeHtml(r.owner) + tag + warn + badge + '</span>' +
|
||||
'<span class="owner"><a class="owner-link" href="https://vexascan.com/account/' +
|
||||
encodeURIComponent(r.owner) + '" target="_blank" rel="noopener">' +
|
||||
escapeHtml(r.owner) + '</a>' + tag + warn + badge + '</span>' +
|
||||
'<span class="stake" data-label="STAKE (VEX)">' + vex(r.staked) + '</span>' +
|
||||
'<span class="reward" data-label="TOTAL REWARD (VEX)">' + vex(r.total_reward) + '</span>' +
|
||||
'<span class="vote-date" data-label="VOTE TERAKHIR">' + shortdate(r.last_vote) + '</span>' +
|
||||
|
||||
@@ -269,6 +269,12 @@ button.pill {
|
||||
color: var(--on-dark);
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.owner-link {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
.owner-link:hover { color: var(--link); }
|
||||
|
||||
/* ————— band BARU / KADALUARSA (V40) ————— */
|
||||
.band-list { margin-top: var(--sp-xl); }
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Saira+Condensed:wght@400&family=EB+Garamond:ital,wght@0,400;1,400&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css', v=9) }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css', v=10) }}">
|
||||
</head>
|
||||
<body>
|
||||
<header class="top-nav">
|
||||
@@ -71,7 +71,7 @@
|
||||
<div class="list-rows">
|
||||
{% for pay in payments %}
|
||||
<div class="list-row">
|
||||
<span class="owner">{{ pay[2] }}</span>
|
||||
<span class="owner"><a class="owner-link" href="https://vexascan.com/account/{{ pay[2] }}" target="_blank" rel="noopener">{{ pay[2] }}</a></span>
|
||||
<span class="stake" data-label="JUMLAH">{{ pay[3]|vex }}</span>
|
||||
<span class="vote-date" data-label="STATUS">{{ pay[5]|upper }}</span>
|
||||
<span class="owner txid" data-label="TXID">{% if pay[4] %}<a href="https://vexascan.com/transaction/{{ pay[4] }}" target="_blank" rel="noopener">{{ pay[4] }}</a>{% else %}—{% endif %}</span>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Saira+Condensed:wght@400&family=EB+Garamond:ital,wght@0,400;1,400&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css', v=15) }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css', v=16) }}">
|
||||
</head>
|
||||
<body>
|
||||
<header class="top-nav">
|
||||
@@ -75,7 +75,7 @@
|
||||
{% for r in search_rows %}
|
||||
<div class="list-row{% if r.expired %} expired{% endif %}">
|
||||
<span class="rank">{{ r.rank }}</span>
|
||||
<span class="owner">{{ r.owner }} <span class="group-tag tag-{{ r.group }}">{{ {'baru': 'BARU', 'revote': 'REVOTE', 'valid': 'VALID', 'kadaluarsa': 'KADALUARSA'}[r.group] }}</span>{% if r.expiring %}<span class="warn-badge">VOTE ULANG SEGERA</span>{% endif %}{% if r.expired %}<span class="expired-badge">VOTE ULANG</span>{% endif %}</span>
|
||||
<span class="owner"><a class="owner-link" href="https://vexascan.com/account/{{ r.owner }}" target="_blank" rel="noopener">{{ r.owner }}</a> <span class="group-tag tag-{{ r.group }}">{{ {'baru': 'BARU', 'revote': 'REVOTE', 'valid': 'VALID', 'kadaluarsa': 'KADALUARSA'}[r.group] }}</span>{% if r.expiring %}<span class="warn-badge">VOTE ULANG SEGERA</span>{% endif %}{% if r.expired %}<span class="expired-badge">VOTE ULANG</span>{% endif %}</span>
|
||||
<span class="stake" data-label="STAKE (VEX)">{{ r.staked|vex }}</span>
|
||||
<span class="reward" data-label="TOTAL REWARD (VEX)">{{ r.total_reward|vex }}</span>
|
||||
<span class="vote-date" data-label="VOTE TERAKHIR">{{ r.last_vote|shortdate }}</span>
|
||||
@@ -102,7 +102,7 @@
|
||||
{% for row in new_rows %}
|
||||
<div class="list-row">
|
||||
<span class="rank">{{ loop.index }}</span>
|
||||
<span class="owner">{{ row[0] }} <span class="group-tag tag-baru">BARU</span></span>
|
||||
<span class="owner"><a class="owner-link" href="https://vexascan.com/account/{{ row[0] }}" target="_blank" rel="noopener">{{ row[0] }}</a> <span class="group-tag tag-baru">BARU</span></span>
|
||||
<span class="stake" data-label="STAKE (VEX)">{{ row[1]|vex }}</span>
|
||||
<span class="reward" data-label="TOTAL REWARD (VEX)">{{ row[3]|vex }}</span>
|
||||
<span class="vote-date" data-label="VOTE TERAKHIR">{{ row[2]|shortdate }}</span>
|
||||
@@ -134,7 +134,7 @@
|
||||
{% for row in expired_rows %}
|
||||
<div class="list-row expired">
|
||||
<span class="rank">{{ loop.index }}</span>
|
||||
<span class="owner">{{ row[0] }} <span class="expired-badge">VOTE ULANG</span></span>
|
||||
<span class="owner"><a class="owner-link" href="https://vexascan.com/account/{{ row[0] }}" target="_blank" rel="noopener">{{ row[0] }}</a> <span class="expired-badge">VOTE ULANG</span></span>
|
||||
<span class="stake" data-label="STAKE (VEX)">{{ row[1]|vex }}</span>
|
||||
<span class="reward" data-label="TOTAL REWARD (VEX)">{{ row[3]|vex }}</span>
|
||||
<span class="vote-date" data-label="VOTE TERAKHIR">{{ row[2]|shortdate }}</span>
|
||||
@@ -143,7 +143,7 @@
|
||||
{% for row in rows %}
|
||||
<div class="list-row">
|
||||
<span class="rank">{{ row[4] + expired_count }}</span>
|
||||
<span class="owner">{{ row[0] }}{% if row[8] %} <span class="group-tag tag-revote">REVOTE</span>{% endif %}{% if row[9] %} <span class="warn-badge">VOTE ULANG SEGERA</span>{% endif %}</span>
|
||||
<span class="owner"><a class="owner-link" href="https://vexascan.com/account/{{ row[0] }}" target="_blank" rel="noopener">{{ row[0] }}</a>{% if row[8] %} <span class="group-tag tag-revote">REVOTE</span>{% endif %}{% if row[9] %} <span class="warn-badge">VOTE ULANG SEGERA</span>{% endif %}</span>
|
||||
<span class="stake" data-label="STAKE (VEX)">{{ row[1]|vex }}</span>
|
||||
<span class="reward" data-label="TOTAL REWARD (VEX)">{{ row[6]|vex }}</span>
|
||||
<span class="vote-date" data-label="VOTE TERAKHIR">{{ row[5]|shortdate }}</span>
|
||||
|
||||
@@ -107,6 +107,9 @@ def main():
|
||||
html = resp.get_data(as_text=True)
|
||||
check('V10 judul halaman', 'DAFTAR PEMILIH' in html)
|
||||
check('V13 kolom VOTE TERAKHIR', 'VOTE TERAKHIR' in html)
|
||||
check('V51 akun jadi link vexascan',
|
||||
'href="https://vexascan.com/account/acct125"' in html
|
||||
and 'target="_blank"' in html and 'owner-link' in html)
|
||||
check('V15 kolom BOBOT SUARA tak tampil', 'BOBOT SUARA' not in html)
|
||||
check('V15 tak ada sel .weight', 'class="weight"' not in html)
|
||||
check('V15 tak ada penanda basi', 'stale-mark' not in html and 'BASI' not in html)
|
||||
@@ -243,6 +246,8 @@ def main():
|
||||
check('V27 tanggal di judul TRANSFER PER PEMILIH',
|
||||
f'TRANSFER PER PEMILIH · {today}' in html)
|
||||
check('V27 transfer per-pemilih tampil', 'acct001' in html)
|
||||
check('V51 history akun jadi link vexascan',
|
||||
'href="https://vexascan.com/account/acct001"' in html)
|
||||
check('V27 hanya run terbaru (transfer lama tak tampil)',
|
||||
'oldacct' not in html)
|
||||
check('V27 status SENT tampil', 'SENT' in html)
|
||||
@@ -258,6 +263,8 @@ def main():
|
||||
check('V11 hanya akun cocok', 'acct125' in html and 'acct120' in html
|
||||
and 'acct119' not in html)
|
||||
check('V11 caption HASIL · 6', 'HASIL · 6 AKUN' in html)
|
||||
check('V51 search akun jadi link vexascan',
|
||||
'href="https://vexascan.com/account/acct125"' in html)
|
||||
q_vals = _extract_stakes(html)
|
||||
check('V11 hasil search urut DESC', q_vals == sorted(q_vals, reverse=True)
|
||||
and len(q_vals) == 6)
|
||||
|
||||
Reference in new issue
Block a user