Commit Graph
13 Commits
Author SHA1 Message Date
tajniak81andClaude Opus 5 9686cae8b9 The charger row asks the same way everything else does
Removing a home charger kept its own inline prompt, written before there
was an app-wide one. Two mechanisms for one question is one too many: it
now calls askConfirm() like every other destructive action, and the panel,
its pending-removal state and the ask/cancel pair go with it. The button
still disables while the delete is in flight.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 12:09:59 +02:00
tajniak81andClaude Opus 5 749f42f481 Remove asks in the page, not in a dialog the browser may refuse to show
Removing a charger was gated behind window.confirm(). A browser that
suppresses native dialogs — an embedded webview, a blocked-dialogs setting
— does not show it and hands back false, so the click answered "no" on the
user's behalf: no request, no error, nothing. The button looked broken and
the endpoint was never reached. It always had been fine; a delete with an
unknown id still answers 404 and the ownership path still resolves.

The prompt is part of the row now — the warning, Cancel, Remove — the way
the charger reset in the same view already asks. Remove disables while the
delete is in flight, and a failure lands in the error line the list
already has.

Fifteen other confirm() call sites share the flaw and are left for their
own change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:56:04 +02:00
tajniak81andClaude Opus 5 21a54c4cfa The bolt in the list takes the colour the badge already had
Every charger in "Your chargers" drew its bolt in the same green, state or
no state — the list's one visual signal said nothing. It now takes the
green and amber the information card's badges use, off the same live map,
so the list answers "which one is up?" without opening anything.

A charger the service is silent about draws muted rather than green: a
green bolt for an unknown charger is a claim. The icon carries a title as
well, so the state is not left to colour alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:52:04 +02:00
tajniak81andClaude Opus 5 7cbc81778c Every row, every time, dashes included
The information card drew a row only when it had a value, so two chargers
side by side had two different shapes and neither could be read against the
other — and a field the service is silent about looked the same as a field
the card never offers. Every row is drawn now, with a dash where there is
nothing to say, which is itself worth seeing.

Service id loses the rule that hid it when it matched the serial. Kept, it
would have printed a dash for a charger that does have one, and a dash that
means "no" where the answer is "the same as above" is worse than the
repetition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:47:23 +02:00
tajniak81andClaude Opus 5 ad785ee9f8 The fields the cloud sends, kept all the way to the card
Normalizing a provider's charger list threw most of the answer away:
firmware, the site id, how the charger is registered, the charge power and
the cloud's own OCPP reading all arrived from Anker and none of them got
past providerCharger, which carried seven fields and dropped the rest. The
information card could not show what it was never handed.

It carries them now, and the card lays them out: firmware beside the model,
site and site id where the charger lives, "Registered as" for standalone /
in a system / bound, and — when the service knows — state, charge power and
OCPP status. Charge power is relayed exactly as worded upstream, since the
unit is theirs and putting one on it here would be inventing it. Settings'
own list gains the firmware in its subtitle. A field no view supplied still
leaves no row, so an account whose chargers stand outside a system reads
shorter rather than emptier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:43:23 +02:00
tajniak81andClaude Opus 5 86ea97b414 Online said out loud, not left as the absence of "Offline"
Settings listed a charger's reachability with one badge, drawn only when
the cloud said online:false. A reachable charger got nothing — the same
nothing a charger the cloud declined to report on gets — so the account's
two chargers read as one offline and one unremarkable. Online now has a
badge of its own, and the silent case stays silent: not saying is not the
same as saying no.

The Charging page said nothing at all, having only the record, which knows
what a charger is and not whether it is answering. So it asks: each
connected service's charger list is read into a live map keyed by the
provider's own id, and the information card carries the badge and, when
the service reports one, the operating state. Asking costs a round trip
per service, so it happens when the home tab is first opened — the moment
the question is being asked — and after that only when the user presses
Refresh or imports a charger. A service that will not answer leaves its
chargers unbadged rather than offline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:22:02 +02:00
tajniak81andClaude Opus 5 4203ca93db What a charger is, readable without a CSMS to control it with
The Home chargers tab put a card where the control panel goes and, with
neither Own nor Proxy CSMS turned on, filled it with a single sentence
pointing at Settings — an empty box beside a list of two real chargers the
app already knew the vendor, model and serial of.

That knowledge now has somewhere to be. Charger information lists every
imported charger with the fields its record actually holds; a field the
service never sent is left out rather than shown as a dash, and the
provider's own id appears only when it is something other than the serial.
The selected charger carries the same accent ring the list beside it does,
so picking one still reads as picking the one control drives. The card
stands on its own either way: with control on it sits beneath it, and with
control off the Settings hint becomes a footnote under something worth
reading instead of the whole card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:12:17 +02:00
tajniak81andClaude Opus 5 a3f69fa5ef Home chargers: your own wallbox as a record, imported the way a car is
The Home chargers tab has been showing a hardcoded "Home charger · 11 kW · NACS"
since it was drawn, and the control card asked for a serial as free text — a
number printed on a box hanging in a garage, typed in by hand while the connected
account already knew it. The garage solved the same problem for cars a while ago,
so this is that solution aimed at the wall: pick the charger off a service you
have connected, press Import, and it becomes a record of yours.

A charger is a record rather than a live listing because it has to outlive the
account it came from. Disconnect Anker and the wallbox is still on the wall; the
integration is how the charger was found, not what it is. Hence home_chargers,
owned by a person and not related to any car — it charges whichever car is
plugged into it, and it outlives all of them — and hence no sharing: a charger is
one household's business in a way a car shared with a partner is not.

The provider layer is vehicleproviders.go's shape on purpose, down to the soft
gate: a listing answers 200 with an empty list and the sentence that says what to
do about a closed gate, a write answers 400, because there the caller asked for
something that did not happen. Anker and Greencell are two adapters over plugins
that already exist, so the next charger service is an adapter appended to
chargerSources() and nothing else. What is deliberately absent is the car
import's checkbox panel: a charger is a name, a serial and the hardware behind
it, all of which the list already carries, so there is nothing to choose and the
whole screen is pick one, press Import.

Only the name is editable afterwards. The rest describes hardware and came from
the service, and the provider link is written by the import endpoint alone, so
renaming a charger cannot quietly orphan it from the account it tracks. Deleting
one says as much in its confirmation: the charger is untouched, and importing it
again brings the record straight back.

The Anker gate moved into ankerGate() beside greencellGate(), because the same
four-case switch was about to exist in a third place. Behaviour is unchanged —
the same sentences, and the probe still skips the personal opt-in, since checking
credentials is what you do before switching the integration on.

The phone app still has the old tab; parity there is a separate change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 21:07:21 +02:00
tajniak81andClaude Opus 5 e138fad3f4 Anker: every charger on the account, not just the ones outside a station
get_user_bind_and_not_in_station_evchargers is the only list the connector ever
asked for, and its name says exactly what it withholds. A charger that belongs to
a system is not in it. Its userBindEvChargersCount, though, counts every charger
bound to the account — so an owner with two chargers in a system got "authenticated;
2 EV charger(s) bound to account" from the health probe and an empty list from the
capability that is supposed to show them. A working login that finds nothing.

So the capability now asks every view the cloud has and merges them by serial. The
standalone list still answers for chargers standing on their own; get_site_list
walks the systems and reads each one through get_scen_info, falling back to
get_system_running_info where that is silent — the power-service / HES split
charger-state already knows; and get_relate_and_bind_devices contributes model,
firmware and the Wi-Fi flag, and discovers anything in the A519 family that the
first two missed. Whichever way a charger was registered, one of the three has it.

The merge is first-writer-wins per field rather than last view overwriting: the
standalone record knows the name, the site record knows the live state, and neither
should blank what the other established. A view that fails is a warning on the
document instead of an error on the call, because one dead endpoint should not
cost the chargers the other two found. Only losing all three is a failure. When
nothing comes back at all the response says so in its own words and names the
remaining suspect — country picks the regional server, and the wrong one
authenticates happily and shows an empty account.

The other half of "not showing any chargers" was that neither client ever showed a
list. The serial was a text box, and the number is printed on a charger hanging on
a wall. Both apps now list what the account holds — name, serial, model, site,
state, an offline badge — and hand the serial to the OCPP control card instead of
asking anyone to go and read it. Where control is off the list still stands on its
own, as the answer to the first question an owner has after entering credentials.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:13:36 +02:00
tajniak81andClaude Opus 4.8 f9b1bcc520 Web App: fold integration cards; split Charging into two tabs
Settings → Integrations: the Toyota and Anker Solix cards are now
collapsible, like the plugin rows in the API Server panel. Each header
is a toggle with a rotating chevron; the Connected badge stays visible
when collapsed. Bodies use v-show so loaded state and in-flight edits
survive folding. Collapsed by default.

Charging: split into "Public chargers" and "Home chargers" tabs. Public
keeps the discovery map (public pins only) + demo session + nearby
public stations. Home holds the real OCPP control card (moved here) and
the user's home charger list, with a hint pointing to Settings when no
control mode is active. Adds charging.tabs.*, stations.homeHeading and
stations.noControlHint (en/pl/da).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:22:38 +02:00
tajniak81andClaude Opus 4.8 19a7d48feb Harden Anker Solix OCPP control (token hashing, step-up, audit, TLS)
Security pass over the OCPP charger-control feature added in a1519f6, since
remotely actuating a physical charger is a real side effect.

Token hygiene:
  - Per-charger control tokens are stored as SHA-256 hashes + a last-4 hint,
    never plaintext. The token is shown once at generation; the status endpoint
    returns only the hint. Added a revoke endpoint that also drops any live
    session using the revoked token.

Step-up + confirmation:
  - Destructive actions (reset, unlock) require confirm:true AND a password
    re-authentication (verified against PocketBase). The Charging UI collects the
    password inline for reset.
  - Per user+charger rate limit (30/min) on control commands.

Transport + provenance:
  - OCPP_REQUIRE_TLS (default on) rejects plaintext ws:// charger connections;
    OCPP_PUBLIC_URL pins the advertised endpoint instead of trusting request
    headers.
  - Proxy-mode upstream URL is validated against a *.anker.com allowlist, so a
    spoofed ocpp-info response can't redirect the proxy.

Durable audit:
  - New control_audit PocketBase collection (added to setup-pocketbase.mjs);
    every control action, token generate/revoke and charger connect is persisted
    best-effort in addition to a structured log line.

Startup:
  - The control-token index is warmed from PocketBase on startup so a charger
    reconnecting after a restart resolves immediately.

Tests:
  - Unit tests for token hashing/eviction/revoke (no plaintext at rest),
    rate limiter, destructive-action classifier, upstream allowlist, TLS
    enforcement, and re-auth guards. A full-stack E2E (control_e2e_test.go)
    drives the real Handler with a stand-in PocketBase and a simulated charge
    point, proving step-up (400/401/200) and audit persistence end to end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 21:03:05 +02:00
tajniak81andClaude Opus 4.8 a1519f6e89 Add OCPP control for the Anker Solix EV charger (Own/Proxy CSMS)
The Anker Solix connector was read-only (cloud monitoring only). Add an
OCPP 1.6J control path with a per-user, cascading control mode:

  - off   monitoring only (default, unchanged behavior)
  - own   DriverVault is the charger's Central System (full control)
  - proxy DriverVault relays to Anker's cloud and injects commands

New internal/ocpp subsystem (stdlib-only, hand-rolled RFC 6455): a CSMS
with session management, inbound dispatch, and typed control commands
(RemoteStart/Stop, SetChargingProfile current limit, ChangeAvailability,
Reset, UnlockConnector, TriggerMessage, Get/ChangeConfiguration). Own- and
proxy-mode paths are verified end-to-end against a simulated charge point.

The charger connects to /ocpp/{serial}, authenticated with OCPP Basic auth
(serial + a per-charger control token) resolved to the owning user via an
in-memory token index. Control REST endpoints mirror the monitoring ones and
reuse the same cascade gate plus a live-session check. controlMode is a new
cascade field (global -> org -> user) advertised as a select on the plugin.

Frontend: control-mode select + provisioning card in Settings, and a real
Start/Stop/limit/reset control panel in Charging, gated on the active mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:40:22 +02:00
tajniak81andClaude Opus 4.8 2eeff15925 Add a Charging tab to the web app sidebar
Mirror the web-dashboard UI kit's charging screen: a map panel with
charger pins, an active-session card, and a nearby-stations list. Adds
the sidebar nav item, the /charging route, and the Charging view, plus
en/pl/da translations. Session and station data are presentational
placeholders until the server exposes charging telemetry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:01:44 +02:00