@mxpicture/tesla-superchargers-http 0.1.37 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,YAAY,CAAC;AAwB1D;;;;GAIG;AACH,eAAO,MAAM,aAAa,QAAO,eAoF/B,CAAC"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,YAAY,CAAC;AAwB1D;;;;GAIG;AACH,eAAO,MAAM,aAAa,QAAO,eAwG/B,CAAC"}
package/dist/commands.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { chargersApi, chargingProcessesApi, failuresApi, invoicesApi, slugsApi, } from "@mxpicture/tesla-superchargers-api/api";
2
2
  import { HttpError } from "./errors.js";
3
- import { coordinate, favourite, flag, id, locale, maxFetches, optString, optTimestamp, radiusKm, source, threshold, } from "./params.js";
3
+ import { chargerSelector, coordinate, favourite, flag, id, locale, maxFetches, optString, optTimestamp, radiusKm, source, threshold, } from "./params.js";
4
4
  /** reset-failures: exactly one of `id` (a single row) or `all` (the source). */
5
5
  const resetFailures = async (params) => {
6
6
  const src = source(params);
@@ -48,15 +48,30 @@ export const buildCommands = () => ({
48
48
  },
49
49
  "fetch-charger": {
50
50
  method: "GET",
51
- run: (p) => chargersApi().fetchSingle(coordinate(p), locale(p), radiusKm(p)),
51
+ run: (p) => {
52
+ const selector = chargerSelector(p);
53
+ if (selector.kind === "id")
54
+ throw new HttpError(400, "'id' is a stored-row lookup — use 'get-charger'");
55
+ return selector.kind === "slug"
56
+ ? chargersApi().fetchBySlug(selector.slug, locale(p))
57
+ : chargersApi().fetchFirstByCoordinates(coordinate(p), locale(p), radiusKm(p));
58
+ },
52
59
  },
53
60
  "fetch-chargers": {
54
61
  method: "GET",
55
- run: (p) => chargersApi().fetch(coordinate(p), locale(p), radiusKm(p), undefined, maxFetches(p)),
62
+ run: (p) => chargersApi().fetchByCoordinates(coordinate(p), locale(p), radiusKm(p), undefined, maxFetches(p)),
56
63
  },
57
64
  "fetch-prices": {
58
65
  method: "GET",
59
- run: (p) => chargersApi().fetchPrices(coordinate(p), optTimestamp(p, "at") ?? new Date(), radiusKm(p)),
66
+ run: (p) => {
67
+ const selector = chargerSelector(p);
68
+ const at = optTimestamp(p, "at") ?? new Date();
69
+ if (selector.kind === "slug")
70
+ return chargersApi().fetchPricesBySlug(selector.slug, at);
71
+ if (selector.kind === "id")
72
+ return chargersApi().fetchPricesById(selector.id, at);
73
+ return chargersApi().fetchPricesByCoordinates(coordinate(p), at, radiusKm(p));
74
+ },
60
75
  },
61
76
  "query-unpriced-charges": {
62
77
  method: "GET",
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
package/dist/params.d.ts CHANGED
@@ -10,8 +10,24 @@ export declare const coordinate: (params: Params) => {
10
10
  longitude: number;
11
11
  };
12
12
  export declare const id: (params: Params) => string;
13
+ export type ChargerSelector = {
14
+ kind: "slug";
15
+ slug: string;
16
+ } | {
17
+ kind: "id";
18
+ id: string;
19
+ } | {
20
+ kind: "coord";
21
+ };
22
+ /**
23
+ * Which key a charger lookup was called with: exactly one of `slug`, `id` or
24
+ * the `lat`+`lon` pair. None or several is a usage error — silently preferring
25
+ * one over the other hides a typo in the query string.
26
+ */
27
+ export declare const chargerSelector: (params: Params) => ChargerSelector;
13
28
  export declare const favourite: (params: Params) => boolean;
14
- export declare const radiusKm: (params: Params) => number;
29
+ /** The lookup radius in km; absent → undefined, so the api's own default applies. */
30
+ export declare const radiusKm: (params: Params) => number | undefined;
15
31
  export declare const locale: (params: Params) => string;
16
32
  /** An optional ISO-8601 timestamp; present but unparseable → 400. */
17
33
  export declare const optTimestamp: (params: Params, key: string) => Date | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAC;AAE9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAMzC,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,OASvD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAOzD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,SAOhE,CAAC;AAcF,0EAA0E;AAC1E,eAAO,MAAM,UAAU,GACrB,QAAQ,MAAM,KACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAGtC,CAAC;AAEH,eAAO,MAAM,EAAE,GAAI,QAAQ,MAAM,WAA8B,CAAC;AAEhE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,YAAmC,CAAC;AAE5E,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,KAAG,MACT,CAAC;AAElC,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,KAAG,MACF,CAAC;AAEvC,qEAAqE;AACrE,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,IAAI,GAAG,SASjE,CAAC;AAIF,uEAAuE;AACvE,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,KAAG,aAAa,GAAG,KAMvD,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,MAQ1C,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,KAAG,MAQ3C,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,IAAI,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,OAGlD,CAAC"}
1
+ {"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAC;AAE9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAMzC,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,OASvD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAOzD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,SAOhE,CAAC;AAqBF,0EAA0E;AAC1E,eAAO,MAAM,UAAU,GACrB,QAAQ,MAAM,KACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAGtC,CAAC;AAEH,eAAO,MAAM,EAAE,GAAI,QAAQ,MAAM,WAA8B,CAAC;AAEhE,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,eAgBhD,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,YAAmC,CAAC;AAE5E,qFAAqF;AACrF,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,SACpB,CAAC;AAEhC,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,KAAG,MACF,CAAC;AAEvC,qEAAqE;AACrE,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,IAAI,GAAG,SASjE,CAAC;AAIF,uEAAuE;AACvE,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,KAAG,aAAa,GAAG,KAMvD,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,MAQ1C,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,KAAG,MAQ3C,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,IAAI,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,OAGlD,CAAC"}
package/dist/params.js CHANGED
@@ -30,6 +30,13 @@ export const optString = (params, key) => {
30
30
  return String(value);
31
31
  return bad(`'${key}' must be a string`);
32
32
  };
33
+ /** A present, finite number; absent/empty → undefined, unparseable → 400. */
34
+ const optNumber = (params, key) => {
35
+ const value = params[key];
36
+ if (value === undefined || value === null || value === "")
37
+ return undefined;
38
+ return numberValue(params, key);
39
+ };
33
40
  const numberValue = (params, key) => {
34
41
  const value = params[key];
35
42
  const n = typeof value === "number"
@@ -47,8 +54,28 @@ export const coordinate = (params) => ({
47
54
  longitude: numberValue(params, "lon"),
48
55
  });
49
56
  export const id = (params) => stringValue(params, "id");
57
+ /**
58
+ * Which key a charger lookup was called with: exactly one of `slug`, `id` or
59
+ * the `lat`+`lon` pair. None or several is a usage error — silently preferring
60
+ * one over the other hides a typo in the query string.
61
+ */
62
+ export const chargerSelector = (params) => {
63
+ const slug = optString(params, "slug");
64
+ const rowId = optString(params, "id");
65
+ const coord = optString(params, "lat") !== undefined ||
66
+ optString(params, "lon") !== undefined;
67
+ const given = [slug !== undefined, rowId !== undefined, coord].filter(Boolean);
68
+ if (given.length !== 1)
69
+ return bad("provide exactly one of 'slug', 'id' or 'lat'+'lon'");
70
+ if (slug !== undefined)
71
+ return { kind: "slug", slug };
72
+ if (rowId !== undefined)
73
+ return { kind: "id", id: rowId };
74
+ return { kind: "coord" };
75
+ };
50
76
  export const favourite = (params) => boolValue(params, "favourite");
51
- export const radiusKm = (params) => numberValue(params, "radiusKm");
77
+ /** The lookup radius in km; absent → undefined, so the api's own default applies. */
78
+ export const radiusKm = (params) => optNumber(params, "radiusKm");
52
79
  export const locale = (params) => params.locale ?? "de-DE";
53
80
  /** An optional ISO-8601 timestamp; present but unparseable → 400. */
54
81
  export const optTimestamp = (params, key) => {
@@ -1 +1 @@
1
- {"version":3,"file":"params.js","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"params.js","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"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"}
package/dist/ui.d.ts CHANGED
@@ -13,5 +13,5 @@
13
13
  * The inner <script> deliberately avoids template literals so it can live inside
14
14
  * this outer template string without escaping.
15
15
  */
16
- export declare const UI_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Tesla Superchargers</title>\n<style>\n :root {\n --bg: #f6f7f9; --card: #ffffff; --ink: #1a1d21; --muted: #6b7280;\n --line: #e4e7eb; --accent: #2563eb; --action: #b45309; --ok: #047857;\n --err: #b91c1c; --code-bg: #0f172a; --code-ink: #e2e8f0;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #0f1216; --card: #171b21; --ink: #e6e8eb; --muted: #9aa4b2;\n --line: #262b33; --accent: #60a5fa; --action: #f59e0b; --ok: #34d399;\n --err: #f87171; --code-bg: #0b0f16; --code-ink: #dbe2ea;\n }\n }\n * { box-sizing: border-box; }\n body {\n margin: 0; background: var(--bg); color: var(--ink);\n font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n }\n header {\n padding: 20px 24px; border-bottom: 1px solid var(--line);\n display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;\n }\n header h1 { margin: 0; font-size: 18px; font-weight: 650; }\n header p { margin: 0; color: var(--muted); font-size: 13px; }\n main {\n max-width: 1100px; margin: 0 auto; padding: 24px;\n display: grid; gap: 16px;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n }\n .card {\n background: var(--card); border: 1px solid var(--line); border-radius: 10px;\n padding: 16px; display: flex; flex-direction: column; gap: 10px;\n }\n .card .title { display: flex; align-items: center; gap: 8px; }\n .card h2 { margin: 0; font-size: 15px; font-weight: 600; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n .badge {\n font-size: 11px; font-weight: 700; letter-spacing: .03em; padding: 2px 6px;\n border-radius: 5px; color: #fff;\n }\n .badge.get { background: var(--accent); }\n .badge.post { background: var(--action); }\n .desc { margin: 0; color: var(--muted); font-size: 13px; }\n .fields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }\n .field { display: flex; flex-direction: column; gap: 3px; }\n .field.wide { grid-column: 1 / -1; }\n .field label { font-size: 12px; color: var(--muted); }\n .field input, .field select {\n padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px;\n background: var(--bg); color: var(--ink); font-size: 14px; width: 100%;\n }\n .row { display: flex; align-items: center; gap: 8px; margin-top: 2px; }\n button {\n align-self: flex-start; padding: 7px 14px; border: 0; border-radius: 7px;\n background: var(--accent); color: #fff; font-size: 14px; font-weight: 600;\n cursor: pointer;\n }\n .card.action button { background: var(--action); }\n button:disabled { opacity: .55; cursor: default; }\n pre.result {\n margin: 0; padding: 10px 12px; border-radius: 8px; background: var(--code-bg);\n color: var(--code-ink); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;\n max-height: 340px; overflow: auto; white-space: pre-wrap; word-break: break-word;\n border-left: 3px solid var(--line);\n }\n pre.result.ok { border-left-color: var(--ok); }\n pre.result.err { border-left-color: var(--err); }\n pre.result.running { border-left-color: var(--accent); color: var(--muted); }\n #favourites { max-width: 1100px; margin: 0 auto; padding: 24px 24px 0; }\n #favourites h2 { margin: 0; font-size: 15px; font-weight: 600; }\n .fav-wrap { overflow-x: auto; }\n table.fav { width: 100%; border-collapse: collapse; font-size: 13px; }\n table.fav th, table.fav td {\n text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line);\n white-space: nowrap;\n }\n table.fav th { color: var(--muted); font-weight: 600; font-size: 12px; }\n table.fav td.num { text-align: right; font-variant-numeric: tabular-nums; }\n table.fav td.msg { color: var(--muted); }\n button.star {\n padding: 0 4px; background: none; color: var(--muted);\n font-size: 18px; line-height: 1;\n }\n button.star.on { color: var(--accent); }\n .fav-controls { display: flex; gap: 8px; }\n .fav-controls input {\n padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px;\n background: var(--bg); color: var(--ink); font-size: 14px; width: 100%; max-width: 260px;\n }\n table.fav th.sortable { cursor: pointer; user-select: none; }\n table.fav th.sortable::after { content: \" \u2195\"; opacity: .35; }\n table.fav th.sortable.asc::after { content: \" \u2191\"; opacity: 1; }\n table.fav th.sortable.desc::after { content: \" \u2193\"; opacity: 1; }\n .fav-pager { display: flex; align-items: center; gap: 10px; }\n .fav-pager button { align-self: center; padding: 5px 12px; font-size: 13px; }\n .fav-pager span { color: var(--muted); font-size: 13px; }\n</style>\n</head>\n<body>\n<header>\n <h1>Tesla Superchargers</h1>\n <p>Run a command against the price-history database. Actions are highlighted and ask to confirm.</p>\n</header>\n<section id=\"favourites\">\n <div class=\"card\">\n <div class=\"title\"><h2>Favourites</h2></div>\n <p class=\"desc\">Charger favourites. Click a star to toggle; the change persists immediately.</p>\n <div class=\"fav-controls\">\n <input id=\"fav-filter\" type=\"text\" placeholder=\"Filter name or site\u2026\" />\n </div>\n <div class=\"fav-wrap\">\n <table class=\"fav\">\n <thead>\n <tr>\n <th>\u2605</th>\n <th class=\"sortable\" data-key=\"name\">Name</th>\n <th class=\"sortable\" data-key=\"common_site_name\">Site</th>\n <th class=\"num sortable\" data-key=\"public_stall_count\">Stalls</th>\n <th class=\"num sortable\" data-key=\"failure_count\">Failures</th>\n <th class=\"num\">Lat / Lon</th>\n </tr>\n </thead>\n <tbody id=\"fav-body\"></tbody>\n </table>\n </div>\n <div class=\"fav-pager\">\n <button id=\"fav-prev\" type=\"button\">Prev</button>\n <span id=\"fav-pageinfo\"></span>\n <button id=\"fav-next\" type=\"button\">Next</button>\n </div>\n <pre id=\"fav-status\" class=\"result\" hidden></pre>\n </div>\n</section>\n<main id=\"commands\"></main>\n<script>\n(function () {\n var F = {\n lat: { name: \"lat\", label: \"Latitude\", type: \"text\", placeholder: \"49.3135644\", required: true },\n lon: { name: \"lon\", label: \"Longitude\", type: \"text\", placeholder: \"12.1445587\", required: true },\n radiusKm: { name: \"radiusKm\", label: \"Radius (km)\", type: \"number\", placeholder: \"default\" },\n locale: { name: \"locale\", label: \"Locale\", type: \"text\", placeholder: \"de-DE\" },\n maxFetches: { name: \"maxFetches\", label: \"Max fetches\", type: \"number\", placeholder: \"default\" },\n at: { name: \"at\", label: \"At (local)\", type: \"datetime-local\", wide: true },\n source: { name: \"source\", label: \"Source\", type: \"select\", options: [\"all\", \"charges\", \"chargers\", \"slugs\"] },\n sourceCon: { name: \"source\", label: \"Source\", type: \"select\", options: [\"charges\", \"chargers\", \"slugs\", \"all\"] },\n threshold: { name: \"threshold\", label: \"Threshold\", type: \"number\", placeholder: \"default\" },\n id: { name: \"id\", label: \"Id\", type: \"text\", placeholder: \"reset all if empty\", wide: true }\n };\n\n var COMMANDS = [\n { name: \"query-slugs\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.radiusKm],\n desc: \"Nearest location slugs (tesla_uuid) to a coordinate.\" },\n { name: \"fetch-chargers\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.locale, F.radiusKm, F.maxFetches],\n desc: \"Nearest chargers to a coordinate. May fetch tesla.com on demand \u2014 can be slow.\" },\n { name: \"fetch-prices\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.at, F.radiusKm],\n desc: \"Supercharger prices valid at a time (default now) for a coordinate.\" },\n { name: \"query-unpriced-charges\", method: \"GET\", kind: \"read\", fields: [],\n desc: \"TeslaMate Supercharger charges with no cost yet.\" },\n { name: \"query-failures\", method: \"GET\", kind: \"read\", fields: [F.source, F.threshold],\n desc: \"Rows with repeated failures at or above the threshold.\" },\n { name: \"query-unmatched-invoices\", method: \"GET\", kind: \"read\", fields: [],\n desc: \"Supercharger charges without a matched invoice.\" },\n { name: \"sync-invoices-dropbox\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Import Tesla charging-history CSV exports from Dropbox.\" },\n { name: \"run-unpriced-charges\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Backfill cost on unpriced charges from stored Supercharger prices.\" },\n { name: \"sync-slugs\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Snapshot-sync pic_slugs against the live Tesla map feed.\" },\n { name: \"reset-failures\", method: \"POST\", kind: \"action\", fields: [F.sourceCon, F.id],\n desc: \"Clear failure state. Leave id empty to reset ALL rows of the source.\",\n buildBody: function (v) { return v.id ? { source: v.source, id: v.id } : { source: v.source, all: true }; } }\n ];\n\n function el(tag, attrs, text) {\n var e = document.createElement(tag);\n if (attrs) for (var k in attrs) e.setAttribute(k, attrs[k]);\n if (text != null) e.textContent = text;\n return e;\n }\n\n function fieldNode(f) {\n var wrap = el(\"div\", { class: \"field\" + (f.wide ? \" wide\" : \"\") });\n wrap.appendChild(el(\"label\", null, f.label));\n var input;\n if (f.type === \"select\") {\n input = el(\"select\", { name: f.name });\n f.options.forEach(function (o) { input.appendChild(el(\"option\", { value: o }, o)); });\n } else {\n input = el(\"input\", { name: f.name, type: f.type });\n if (f.placeholder) input.setAttribute(\"placeholder\", f.placeholder);\n }\n wrap.appendChild(input);\n return wrap;\n }\n\n function collect(cmd, form) {\n var values = {};\n cmd.fields.forEach(function (f) {\n var input = form.elements[f.name];\n var val = input ? String(input.value).trim() : \"\";\n if (f.type === \"datetime-local\" && val) val = new Date(val).toISOString();\n if (val !== \"\") values[f.name] = val;\n });\n return values;\n }\n\n function show(out, cls, text) {\n out.hidden = false;\n out.className = \"result \" + cls;\n out.textContent = text;\n }\n\n function pretty(text) {\n try { return JSON.stringify(JSON.parse(text), null, 2); } catch (e) { return text; }\n }\n\n function run(cmd, form, out, btn) {\n var values = collect(cmd, form);\n for (var i = 0; i < cmd.fields.length; i++) {\n var f = cmd.fields[i];\n if (f.required && !values[f.name]) { show(out, \"err\", \"Please fill in \" + f.label + \".\"); return; }\n }\n\n var url = \"/\" + cmd.name;\n var opts = { method: cmd.method, headers: {} };\n if (cmd.method === \"GET\") {\n var qs = new URLSearchParams(values).toString();\n if (qs) url += \"?\" + qs;\n } else {\n if (!window.confirm(\"Run \" + cmd.name + \"? This performs a real action.\")) return;\n var body = cmd.buildBody ? cmd.buildBody(values) : values;\n opts.headers[\"content-type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n\n btn.disabled = true;\n show(out, \"running\", \"Running\u2026\");\n fetch(url, opts).then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) show(out, \"ok\", pretty(text));\n else if (res.status === 401) show(out, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(out, \"err\", res.status + \" \u2014 \" + pretty(text));\n });\n }).catch(function (e) {\n show(out, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n }).then(function () { btn.disabled = false; });\n }\n\n function card(cmd) {\n var form = el(\"form\", { class: \"card \" + cmd.kind });\n var title = el(\"div\", { class: \"title\" });\n title.appendChild(el(\"span\", { class: \"badge \" + (cmd.method === \"GET\" ? \"get\" : \"post\") }, cmd.method));\n title.appendChild(el(\"h2\", null, cmd.name));\n form.appendChild(title);\n form.appendChild(el(\"p\", { class: \"desc\" }, cmd.desc));\n\n if (cmd.fields.length) {\n var fields = el(\"div\", { class: \"fields\" });\n cmd.fields.forEach(function (f) { fields.appendChild(fieldNode(f)); });\n form.appendChild(fields);\n }\n\n var btn = el(\"button\", { type: \"submit\" }, cmd.kind === \"action\" ? \"Run action\" : \"Run\");\n form.appendChild(btn);\n var out = el(\"pre\", { class: \"result\", hidden: \"hidden\" });\n form.appendChild(out);\n\n form.addEventListener(\"submit\", function (e) { e.preventDefault(); run(cmd, form, out, btn); });\n return form;\n }\n\n var favBody = document.getElementById(\"fav-body\");\n var favStatus = document.getElementById(\"fav-status\");\n var favFilterInput = document.getElementById(\"fav-filter\");\n var favPrev = document.getElementById(\"fav-prev\");\n var favNext = document.getElementById(\"fav-next\");\n var favPageInfo = document.getElementById(\"fav-pageinfo\");\n var favSortables = document.querySelectorAll(\"#favourites th.sortable\");\n\n var FAV_PAGE_SIZE = 10;\n var favRows = []; // full dataset from the server\n var favFilter = \"\"; // lower-cased name/site filter\n var favSort = { key: \"\", dir: 1 }; // dir: 1 asc, -1 desc\n var favPage = 0; // zero-based page index\n\n function favMessage(text) {\n favBody.innerHTML = \"\";\n var tr = el(\"tr\");\n tr.appendChild(el(\"td\", { colspan: \"6\", class: \"msg\" }, text));\n favBody.appendChild(tr);\n }\n\n function setStar(btn, on) {\n btn.textContent = on ? \"\u2605\" : \"\u2606\";\n btn.className = \"star\" + (on ? \" on\" : \"\");\n }\n\n function toggleFav(row, btn) {\n var next = !row.favourite;\n btn.disabled = true;\n setStar(btn, next); // optimistic\n fetch(\"/update-fav-charger\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ id: String(row.id), favourite: next }),\n }).then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) {\n row.favourite = JSON.parse(text).favourite;\n setStar(btn, row.favourite);\n favStatus.hidden = true;\n } else {\n setStar(btn, row.favourite); // revert\n if (res.status === 401) show(favStatus, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(favStatus, \"err\", res.status + \" \u2014 \" + pretty(text));\n }\n });\n }).catch(function (e) {\n setStar(btn, row.favourite); // revert\n show(favStatus, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n }).then(function () { btn.disabled = false; });\n }\n\n function starButton(row) {\n var btn = el(\"button\", { type: \"button\", class: \"star\" + (row.favourite ? \" on\" : \"\"), title: \"Toggle favourite\" }, row.favourite ? \"\u2605\" : \"\u2606\");\n btn.addEventListener(\"click\", function () { toggleFav(row, btn); });\n return btn;\n }\n\n function coord(n) {\n return typeof n === \"number\" ? n.toFixed(4) : \"\u2014\";\n }\n\n function favCmp(a, b) {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b));\n }\n\n function favRowNode(row) {\n var tr = el(\"tr\");\n var star = el(\"td\");\n star.appendChild(starButton(row));\n tr.appendChild(star);\n tr.appendChild(el(\"td\", null, row.name));\n tr.appendChild(el(\"td\", null, row.common_site_name));\n tr.appendChild(el(\"td\", { class: \"num\" }, String(row.public_stall_count)));\n tr.appendChild(el(\"td\", { class: \"num\" }, String(row.failure_count)));\n tr.appendChild(el(\"td\", { class: \"num\" }, coord(row.latitude) + \" / \" + coord(row.longitude)));\n return tr;\n }\n\n // filter -> sort against the full dataset, returning the rows to page through.\n function favView() {\n var rows = favRows;\n if (favFilter) {\n rows = rows.filter(function (r) {\n return String(r.name || \"\").toLowerCase().indexOf(favFilter) >= 0 ||\n String(r.common_site_name || \"\").toLowerCase().indexOf(favFilter) >= 0;\n });\n }\n if (favSort.key) {\n var k = favSort.key, dir = favSort.dir;\n rows = rows.slice().sort(function (a, b) { return favCmp(a[k], b[k]) * dir; });\n }\n return rows;\n }\n\n function updateFavPager(total, pages) {\n favPrev.disabled = favPage <= 0;\n favNext.disabled = favPage >= pages - 1;\n if (!total) { favPageInfo.textContent = \"0 of 0\"; return; }\n var from = favPage * FAV_PAGE_SIZE + 1;\n var to = Math.min(total, (favPage + 1) * FAV_PAGE_SIZE);\n favPageInfo.textContent = from + \"\u2013\" + to + \" of \" + total;\n }\n\n function updateFavSortIndicators() {\n Array.prototype.forEach.call(favSortables, function (th) {\n th.classList.remove(\"asc\", \"desc\");\n if (th.getAttribute(\"data-key\") === favSort.key) {\n th.classList.add(favSort.dir > 0 ? \"asc\" : \"desc\");\n }\n });\n }\n\n function renderFav() {\n var rows = favView();\n var pages = Math.max(1, Math.ceil(rows.length / FAV_PAGE_SIZE));\n if (favPage > pages - 1) favPage = pages - 1;\n if (favPage < 0) favPage = 0;\n\n favBody.innerHTML = \"\";\n if (!favRows.length) favMessage(\"No favourites yet.\");\n else if (!rows.length) favMessage(\"No matches.\");\n else {\n var start = favPage * FAV_PAGE_SIZE;\n rows.slice(start, start + FAV_PAGE_SIZE).forEach(function (row) {\n favBody.appendChild(favRowNode(row));\n });\n }\n updateFavPager(rows.length, pages);\n updateFavSortIndicators();\n }\n\n function loadFavourites() {\n show(favStatus, \"running\", \"Loading favourites\u2026\");\n fetch(\"/query-fav-chargers\").then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) { favRows = JSON.parse(text); favPage = 0; renderFav(); favStatus.hidden = true; }\n else if (res.status === 401) show(favStatus, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(favStatus, \"err\", res.status + \" \u2014 \" + pretty(text));\n });\n }).catch(function (e) {\n show(favStatus, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n });\n }\n\n favFilterInput.addEventListener(\"input\", function () {\n favFilter = favFilterInput.value.trim().toLowerCase();\n favPage = 0;\n renderFav();\n });\n favPrev.addEventListener(\"click\", function () { favPage -= 1; renderFav(); });\n favNext.addEventListener(\"click\", function () { favPage += 1; renderFav(); });\n Array.prototype.forEach.call(favSortables, function (th) {\n th.addEventListener(\"click\", function () {\n var key = th.getAttribute(\"data-key\");\n if (favSort.key === key) favSort.dir = -favSort.dir;\n else { favSort.key = key; favSort.dir = 1; }\n favPage = 0;\n renderFav();\n });\n });\n\n var root = document.getElementById(\"commands\");\n COMMANDS.forEach(function (cmd) { root.appendChild(card(cmd)); });\n loadFavourites();\n})();\n</script>\n</body>\n</html>\n";
16
+ export declare const UI_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Tesla Superchargers</title>\n<style>\n :root {\n --bg: #f6f7f9; --card: #ffffff; --ink: #1a1d21; --muted: #6b7280;\n --line: #e4e7eb; --accent: #2563eb; --action: #b45309; --ok: #047857;\n --err: #b91c1c; --code-bg: #0f172a; --code-ink: #e2e8f0;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #0f1216; --card: #171b21; --ink: #e6e8eb; --muted: #9aa4b2;\n --line: #262b33; --accent: #60a5fa; --action: #f59e0b; --ok: #34d399;\n --err: #f87171; --code-bg: #0b0f16; --code-ink: #dbe2ea;\n }\n }\n * { box-sizing: border-box; }\n body {\n margin: 0; background: var(--bg); color: var(--ink);\n font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n }\n header {\n padding: 20px 24px; border-bottom: 1px solid var(--line);\n display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;\n }\n header h1 { margin: 0; font-size: 18px; font-weight: 650; }\n header p { margin: 0; color: var(--muted); font-size: 13px; }\n main {\n max-width: 1100px; margin: 0 auto; padding: 24px;\n display: grid; gap: 16px;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n }\n .card {\n background: var(--card); border: 1px solid var(--line); border-radius: 10px;\n padding: 16px; display: flex; flex-direction: column; gap: 10px;\n }\n .card .title { display: flex; align-items: center; gap: 8px; }\n .card h2 { margin: 0; font-size: 15px; font-weight: 600; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n .badge {\n font-size: 11px; font-weight: 700; letter-spacing: .03em; padding: 2px 6px;\n border-radius: 5px; color: #fff;\n }\n .badge.get { background: var(--accent); }\n .badge.post { background: var(--action); }\n .desc { margin: 0; color: var(--muted); font-size: 13px; }\n .fields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }\n .field { display: flex; flex-direction: column; gap: 3px; }\n .field.wide { grid-column: 1 / -1; }\n .field label { font-size: 12px; color: var(--muted); }\n .field input, .field select {\n padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px;\n background: var(--bg); color: var(--ink); font-size: 14px; width: 100%;\n }\n .row { display: flex; align-items: center; gap: 8px; margin-top: 2px; }\n button {\n align-self: flex-start; padding: 7px 14px; border: 0; border-radius: 7px;\n background: var(--accent); color: #fff; font-size: 14px; font-weight: 600;\n cursor: pointer;\n }\n .card.action button { background: var(--action); }\n button:disabled { opacity: .55; cursor: default; }\n pre.result {\n margin: 0; padding: 10px 12px; border-radius: 8px; background: var(--code-bg);\n color: var(--code-ink); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;\n max-height: 340px; overflow: auto; white-space: pre-wrap; word-break: break-word;\n border-left: 3px solid var(--line);\n }\n pre.result.ok { border-left-color: var(--ok); }\n pre.result.err { border-left-color: var(--err); }\n pre.result.running { border-left-color: var(--accent); color: var(--muted); }\n #favourites { max-width: 1100px; margin: 0 auto; padding: 24px 24px 0; }\n #favourites h2 { margin: 0; font-size: 15px; font-weight: 600; }\n .fav-wrap { overflow-x: auto; }\n table.fav { width: 100%; border-collapse: collapse; font-size: 13px; }\n table.fav th, table.fav td {\n text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line);\n white-space: nowrap;\n }\n table.fav th { color: var(--muted); font-weight: 600; font-size: 12px; }\n table.fav td.num { text-align: right; font-variant-numeric: tabular-nums; }\n table.fav td.msg { color: var(--muted); }\n button.star {\n padding: 0 4px; background: none; color: var(--muted);\n font-size: 18px; line-height: 1;\n }\n button.star.on { color: var(--accent); }\n .fav-controls { display: flex; gap: 8px; }\n .fav-controls input {\n padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px;\n background: var(--bg); color: var(--ink); font-size: 14px; width: 100%; max-width: 260px;\n }\n table.fav th.sortable { cursor: pointer; user-select: none; }\n table.fav th.sortable::after { content: \" \u2195\"; opacity: .35; }\n table.fav th.sortable.asc::after { content: \" \u2191\"; opacity: 1; }\n table.fav th.sortable.desc::after { content: \" \u2193\"; opacity: 1; }\n .fav-pager { display: flex; align-items: center; gap: 10px; }\n .fav-pager button { align-self: center; padding: 5px 12px; font-size: 13px; }\n .fav-pager span { color: var(--muted); font-size: 13px; }\n</style>\n</head>\n<body>\n<header>\n <h1>Tesla Superchargers</h1>\n <p>Run a command against the price-history database. Actions are highlighted and ask to confirm.</p>\n</header>\n<section id=\"favourites\">\n <div class=\"card\">\n <div class=\"title\"><h2>Favourites</h2></div>\n <p class=\"desc\">Charger favourites. Click a star to toggle; the change persists immediately.</p>\n <div class=\"fav-controls\">\n <input id=\"fav-filter\" type=\"text\" placeholder=\"Filter name or site\u2026\" />\n </div>\n <div class=\"fav-wrap\">\n <table class=\"fav\">\n <thead>\n <tr>\n <th>\u2605</th>\n <th class=\"sortable\" data-key=\"name\">Name</th>\n <th class=\"sortable\" data-key=\"common_site_name\">Site</th>\n <th class=\"num sortable\" data-key=\"public_stall_count\">Stalls</th>\n <th class=\"num sortable\" data-key=\"failure_count\">Failures</th>\n <th class=\"num\">Lat / Lon</th>\n </tr>\n </thead>\n <tbody id=\"fav-body\"></tbody>\n </table>\n </div>\n <div class=\"fav-pager\">\n <button id=\"fav-prev\" type=\"button\">Prev</button>\n <span id=\"fav-pageinfo\"></span>\n <button id=\"fav-next\" type=\"button\">Next</button>\n </div>\n <pre id=\"fav-status\" class=\"result\" hidden></pre>\n </div>\n</section>\n<main id=\"commands\"></main>\n<script>\n(function () {\n var F = {\n lat: { name: \"lat\", label: \"Latitude\", type: \"text\", placeholder: \"49.3135644\", required: true },\n lon: { name: \"lon\", label: \"Longitude\", type: \"text\", placeholder: \"12.1445587\", required: true },\n radiusKm: { name: \"radiusKm\", label: \"Radius (km)\", type: \"number\", placeholder: \"default\" },\n locale: { name: \"locale\", label: \"Locale\", type: \"text\", placeholder: \"de-DE\" },\n maxFetches: { name: \"maxFetches\", label: \"Max fetches\", type: \"number\", placeholder: \"default\" },\n at: { name: \"at\", label: \"At (local)\", type: \"datetime-local\", wide: true },\n source: { name: \"source\", label: \"Source\", type: \"select\", options: [\"all\", \"charges\", \"chargers\", \"slugs\"] },\n sourceCon: { name: \"source\", label: \"Source\", type: \"select\", options: [\"charges\", \"chargers\", \"slugs\", \"all\"] },\n threshold: { name: \"threshold\", label: \"Threshold\", type: \"number\", placeholder: \"default\" },\n id: { name: \"id\", label: \"Id\", type: \"text\", placeholder: \"reset all if empty\", wide: true },\n latOpt: { name: \"lat\", label: \"Latitude\", type: \"text\", placeholder: \"49.3135644\" },\n lonOpt: { name: \"lon\", label: \"Longitude\", type: \"text\", placeholder: \"12.1445587\" },\n slug: { name: \"slug\", label: \"Slug\", type: \"text\", placeholder: \"location_url_slug\", wide: true },\n chargerId: { name: \"id\", label: \"Charger id\", type: \"text\", placeholder: \"pic_chargers id\" }\n };\n\n var COMMANDS = [\n { name: \"query-slugs\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.radiusKm],\n desc: \"Nearest location slugs (tesla_uuid) to a coordinate.\" },\n { name: \"fetch-chargers\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.locale, F.radiusKm, F.maxFetches],\n desc: \"Nearest chargers to a coordinate. May fetch tesla.com on demand \u2014 can be slow.\" },\n { name: \"fetch-prices\", method: \"GET\", kind: \"read\", fields: [F.latOpt, F.lonOpt, F.slug, F.chargerId, F.at, F.radiusKm],\n desc: \"Supercharger prices valid at a time (default now), by coordinate, slug or charger id.\" },\n { name: \"query-unpriced-charges\", method: \"GET\", kind: \"read\", fields: [],\n desc: \"TeslaMate Supercharger charges with no cost yet.\" },\n { name: \"query-failures\", method: \"GET\", kind: \"read\", fields: [F.source, F.threshold],\n desc: \"Rows with repeated failures at or above the threshold.\" },\n { name: \"query-unmatched-invoices\", method: \"GET\", kind: \"read\", fields: [],\n desc: \"Supercharger charges without a matched invoice.\" },\n { name: \"sync-invoices-dropbox\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Import Tesla charging-history CSV exports from Dropbox.\" },\n { name: \"run-unpriced-charges\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Backfill cost on unpriced charges from stored Supercharger prices.\" },\n { name: \"sync-slugs\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Snapshot-sync pic_slugs against the live Tesla map feed.\" },\n { name: \"reset-failures\", method: \"POST\", kind: \"action\", fields: [F.sourceCon, F.id],\n desc: \"Clear failure state. Leave id empty to reset ALL rows of the source.\",\n buildBody: function (v) { return v.id ? { source: v.source, id: v.id } : { source: v.source, all: true }; } }\n ];\n\n function el(tag, attrs, text) {\n var e = document.createElement(tag);\n if (attrs) for (var k in attrs) e.setAttribute(k, attrs[k]);\n if (text != null) e.textContent = text;\n return e;\n }\n\n function fieldNode(f) {\n var wrap = el(\"div\", { class: \"field\" + (f.wide ? \" wide\" : \"\") });\n wrap.appendChild(el(\"label\", null, f.label));\n var input;\n if (f.type === \"select\") {\n input = el(\"select\", { name: f.name });\n f.options.forEach(function (o) { input.appendChild(el(\"option\", { value: o }, o)); });\n } else {\n input = el(\"input\", { name: f.name, type: f.type });\n if (f.placeholder) input.setAttribute(\"placeholder\", f.placeholder);\n }\n wrap.appendChild(input);\n return wrap;\n }\n\n function collect(cmd, form) {\n var values = {};\n cmd.fields.forEach(function (f) {\n var input = form.elements[f.name];\n var val = input ? String(input.value).trim() : \"\";\n if (f.type === \"datetime-local\" && val) val = new Date(val).toISOString();\n if (val !== \"\") values[f.name] = val;\n });\n return values;\n }\n\n function show(out, cls, text) {\n out.hidden = false;\n out.className = \"result \" + cls;\n out.textContent = text;\n }\n\n function pretty(text) {\n try { return JSON.stringify(JSON.parse(text), null, 2); } catch (e) { return text; }\n }\n\n function run(cmd, form, out, btn) {\n var values = collect(cmd, form);\n for (var i = 0; i < cmd.fields.length; i++) {\n var f = cmd.fields[i];\n if (f.required && !values[f.name]) { show(out, \"err\", \"Please fill in \" + f.label + \".\"); return; }\n }\n\n var url = \"/\" + cmd.name;\n var opts = { method: cmd.method, headers: {} };\n if (cmd.method === \"GET\") {\n var qs = new URLSearchParams(values).toString();\n if (qs) url += \"?\" + qs;\n } else {\n if (!window.confirm(\"Run \" + cmd.name + \"? This performs a real action.\")) return;\n var body = cmd.buildBody ? cmd.buildBody(values) : values;\n opts.headers[\"content-type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n\n btn.disabled = true;\n show(out, \"running\", \"Running\u2026\");\n fetch(url, opts).then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) show(out, \"ok\", pretty(text));\n else if (res.status === 401) show(out, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(out, \"err\", res.status + \" \u2014 \" + pretty(text));\n });\n }).catch(function (e) {\n show(out, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n }).then(function () { btn.disabled = false; });\n }\n\n function card(cmd) {\n var form = el(\"form\", { class: \"card \" + cmd.kind });\n var title = el(\"div\", { class: \"title\" });\n title.appendChild(el(\"span\", { class: \"badge \" + (cmd.method === \"GET\" ? \"get\" : \"post\") }, cmd.method));\n title.appendChild(el(\"h2\", null, cmd.name));\n form.appendChild(title);\n form.appendChild(el(\"p\", { class: \"desc\" }, cmd.desc));\n\n if (cmd.fields.length) {\n var fields = el(\"div\", { class: \"fields\" });\n cmd.fields.forEach(function (f) { fields.appendChild(fieldNode(f)); });\n form.appendChild(fields);\n }\n\n var btn = el(\"button\", { type: \"submit\" }, cmd.kind === \"action\" ? \"Run action\" : \"Run\");\n form.appendChild(btn);\n var out = el(\"pre\", { class: \"result\", hidden: \"hidden\" });\n form.appendChild(out);\n\n form.addEventListener(\"submit\", function (e) { e.preventDefault(); run(cmd, form, out, btn); });\n return form;\n }\n\n var favBody = document.getElementById(\"fav-body\");\n var favStatus = document.getElementById(\"fav-status\");\n var favFilterInput = document.getElementById(\"fav-filter\");\n var favPrev = document.getElementById(\"fav-prev\");\n var favNext = document.getElementById(\"fav-next\");\n var favPageInfo = document.getElementById(\"fav-pageinfo\");\n var favSortables = document.querySelectorAll(\"#favourites th.sortable\");\n\n var FAV_PAGE_SIZE = 10;\n var favRows = []; // full dataset from the server\n var favFilter = \"\"; // lower-cased name/site filter\n var favSort = { key: \"\", dir: 1 }; // dir: 1 asc, -1 desc\n var favPage = 0; // zero-based page index\n\n function favMessage(text) {\n favBody.innerHTML = \"\";\n var tr = el(\"tr\");\n tr.appendChild(el(\"td\", { colspan: \"6\", class: \"msg\" }, text));\n favBody.appendChild(tr);\n }\n\n function setStar(btn, on) {\n btn.textContent = on ? \"\u2605\" : \"\u2606\";\n btn.className = \"star\" + (on ? \" on\" : \"\");\n }\n\n function toggleFav(row, btn) {\n var next = !row.favourite;\n btn.disabled = true;\n setStar(btn, next); // optimistic\n fetch(\"/update-fav-charger\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ id: String(row.id), favourite: next }),\n }).then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) {\n row.favourite = JSON.parse(text).favourite;\n setStar(btn, row.favourite);\n favStatus.hidden = true;\n } else {\n setStar(btn, row.favourite); // revert\n if (res.status === 401) show(favStatus, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(favStatus, \"err\", res.status + \" \u2014 \" + pretty(text));\n }\n });\n }).catch(function (e) {\n setStar(btn, row.favourite); // revert\n show(favStatus, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n }).then(function () { btn.disabled = false; });\n }\n\n function starButton(row) {\n var btn = el(\"button\", { type: \"button\", class: \"star\" + (row.favourite ? \" on\" : \"\"), title: \"Toggle favourite\" }, row.favourite ? \"\u2605\" : \"\u2606\");\n btn.addEventListener(\"click\", function () { toggleFav(row, btn); });\n return btn;\n }\n\n function coord(n) {\n return typeof n === \"number\" ? n.toFixed(4) : \"\u2014\";\n }\n\n function favCmp(a, b) {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b));\n }\n\n function favRowNode(row) {\n var tr = el(\"tr\");\n var star = el(\"td\");\n star.appendChild(starButton(row));\n tr.appendChild(star);\n tr.appendChild(el(\"td\", null, row.name));\n tr.appendChild(el(\"td\", null, row.common_site_name));\n tr.appendChild(el(\"td\", { class: \"num\" }, String(row.public_stall_count)));\n tr.appendChild(el(\"td\", { class: \"num\" }, String(row.failure_count)));\n tr.appendChild(el(\"td\", { class: \"num\" }, coord(row.latitude) + \" / \" + coord(row.longitude)));\n return tr;\n }\n\n // filter -> sort against the full dataset, returning the rows to page through.\n function favView() {\n var rows = favRows;\n if (favFilter) {\n rows = rows.filter(function (r) {\n return String(r.name || \"\").toLowerCase().indexOf(favFilter) >= 0 ||\n String(r.common_site_name || \"\").toLowerCase().indexOf(favFilter) >= 0;\n });\n }\n if (favSort.key) {\n var k = favSort.key, dir = favSort.dir;\n rows = rows.slice().sort(function (a, b) { return favCmp(a[k], b[k]) * dir; });\n }\n return rows;\n }\n\n function updateFavPager(total, pages) {\n favPrev.disabled = favPage <= 0;\n favNext.disabled = favPage >= pages - 1;\n if (!total) { favPageInfo.textContent = \"0 of 0\"; return; }\n var from = favPage * FAV_PAGE_SIZE + 1;\n var to = Math.min(total, (favPage + 1) * FAV_PAGE_SIZE);\n favPageInfo.textContent = from + \"\u2013\" + to + \" of \" + total;\n }\n\n function updateFavSortIndicators() {\n Array.prototype.forEach.call(favSortables, function (th) {\n th.classList.remove(\"asc\", \"desc\");\n if (th.getAttribute(\"data-key\") === favSort.key) {\n th.classList.add(favSort.dir > 0 ? \"asc\" : \"desc\");\n }\n });\n }\n\n function renderFav() {\n var rows = favView();\n var pages = Math.max(1, Math.ceil(rows.length / FAV_PAGE_SIZE));\n if (favPage > pages - 1) favPage = pages - 1;\n if (favPage < 0) favPage = 0;\n\n favBody.innerHTML = \"\";\n if (!favRows.length) favMessage(\"No favourites yet.\");\n else if (!rows.length) favMessage(\"No matches.\");\n else {\n var start = favPage * FAV_PAGE_SIZE;\n rows.slice(start, start + FAV_PAGE_SIZE).forEach(function (row) {\n favBody.appendChild(favRowNode(row));\n });\n }\n updateFavPager(rows.length, pages);\n updateFavSortIndicators();\n }\n\n function loadFavourites() {\n show(favStatus, \"running\", \"Loading favourites\u2026\");\n fetch(\"/query-fav-chargers\").then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) { favRows = JSON.parse(text); favPage = 0; renderFav(); favStatus.hidden = true; }\n else if (res.status === 401) show(favStatus, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(favStatus, \"err\", res.status + \" \u2014 \" + pretty(text));\n });\n }).catch(function (e) {\n show(favStatus, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n });\n }\n\n favFilterInput.addEventListener(\"input\", function () {\n favFilter = favFilterInput.value.trim().toLowerCase();\n favPage = 0;\n renderFav();\n });\n favPrev.addEventListener(\"click\", function () { favPage -= 1; renderFav(); });\n favNext.addEventListener(\"click\", function () { favPage += 1; renderFav(); });\n Array.prototype.forEach.call(favSortables, function (th) {\n th.addEventListener(\"click\", function () {\n var key = th.getAttribute(\"data-key\");\n if (favSort.key === key) favSort.dir = -favSort.dir;\n else { favSort.key = key; favSort.dir = 1; }\n favPage = 0;\n renderFav();\n });\n });\n\n var root = document.getElementById(\"commands\");\n COMMANDS.forEach(function (cmd) { root.appendChild(card(cmd)); });\n loadFavourites();\n})();\n</script>\n</body>\n</html>\n";
17
17
  //# sourceMappingURL=ui.d.ts.map
package/dist/ui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO,stnBAicnB,CAAC"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO,uwoBAqcnB,CAAC"}
package/dist/ui.js CHANGED
@@ -164,7 +164,11 @@ export const UI_HTML = `<!doctype html>
164
164
  source: { name: "source", label: "Source", type: "select", options: ["all", "charges", "chargers", "slugs"] },
165
165
  sourceCon: { name: "source", label: "Source", type: "select", options: ["charges", "chargers", "slugs", "all"] },
166
166
  threshold: { name: "threshold", label: "Threshold", type: "number", placeholder: "default" },
167
- id: { name: "id", label: "Id", type: "text", placeholder: "reset all if empty", wide: true }
167
+ id: { name: "id", label: "Id", type: "text", placeholder: "reset all if empty", wide: true },
168
+ latOpt: { name: "lat", label: "Latitude", type: "text", placeholder: "49.3135644" },
169
+ lonOpt: { name: "lon", label: "Longitude", type: "text", placeholder: "12.1445587" },
170
+ slug: { name: "slug", label: "Slug", type: "text", placeholder: "location_url_slug", wide: true },
171
+ chargerId: { name: "id", label: "Charger id", type: "text", placeholder: "pic_chargers id" }
168
172
  };
169
173
 
170
174
  var COMMANDS = [
@@ -172,8 +176,8 @@ export const UI_HTML = `<!doctype html>
172
176
  desc: "Nearest location slugs (tesla_uuid) to a coordinate." },
173
177
  { name: "fetch-chargers", method: "GET", kind: "read", fields: [F.lat, F.lon, F.locale, F.radiusKm, F.maxFetches],
174
178
  desc: "Nearest chargers to a coordinate. May fetch tesla.com on demand — can be slow." },
175
- { name: "fetch-prices", method: "GET", kind: "read", fields: [F.lat, F.lon, F.at, F.radiusKm],
176
- desc: "Supercharger prices valid at a time (default now) for a coordinate." },
179
+ { name: "fetch-prices", method: "GET", kind: "read", fields: [F.latOpt, F.lonOpt, F.slug, F.chargerId, F.at, F.radiusKm],
180
+ desc: "Supercharger prices valid at a time (default now), by coordinate, slug or charger id." },
177
181
  { name: "query-unpriced-charges", method: "GET", kind: "read", fields: [],
178
182
  desc: "TeslaMate Supercharger charges with no cost yet." },
179
183
  { name: "query-failures", method: "GET", kind: "read", fields: [F.source, F.threshold],
package/dist/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAictB,CAAC"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqctB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mxpicture/tesla-superchargers-http",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "Http server for interacting with superchargers",
5
5
  "type": "module",
6
6
  "author": "MXPicture",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@mxpicture/tesla-superchargers-api": "^0.1.37",
36
- "@mxpicture/tesla-superchargers-mate": "^0.1.37"
35
+ "@mxpicture/tesla-superchargers-api": "^0.1.38",
36
+ "@mxpicture/tesla-superchargers-mate": "^0.1.38"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^25.2.3",