Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .allmystuff-rev
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.2.45
v0.2.46
6 changes: 3 additions & 3 deletions gui/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ tauri-plugin-single-instance = "2"
# media planes, and the node control socket the client drives. Reused in "CEC
# client (customer) mode". Git deps on the sibling branch that adds the CEC
# node commands; not yet published, so this won't resolve in this sandbox.
allmystuff-node = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.45" }
allmystuff-node = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.46" }
# The CEC wire contract + Support-ID derivation, and the three-choice consent
# store — the customer's per-technician approvals.
allmystuff-cec-protocol = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.45" }
allmystuff-cec-consent = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.45" }
allmystuff-cec-protocol = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.46" }
allmystuff-cec-consent = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.46" }

# The client's OWN OS-service installer (never AllMyStuff's), so installing the
# background service never clobbers an existing AllMyStuff install.
Expand Down
15 changes: 15 additions & 0 deletions gui/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,20 @@ async fn cec_grants(state: State<'_, AppState>) -> Result<Value, String> {
Ok(v)
}

/// What each connected technician is actually doing right now — the node's
/// `cec_viewing` projection `{ techs: { <tech>: { screen, control } } }`,
/// derived from live routes rather than session state. The pull half of the
/// `cec://viewing` event (which the generic pump already relays), so the
/// access list paints the chip correctly on a mid-session app start.
#[tauri::command]
async fn cec_viewing(state: State<'_, AppState>) -> Result<Value, String> {
state
.node
.request("cec_viewing", json!({}))
.await
.map_err(|e| e.to_string())
}

/// Set this computer's friendly name (shown to the technician on the mesh). A
/// convenience beyond the core dial/approve contract.
#[tauri::command]
Expand Down Expand Up @@ -960,6 +974,7 @@ fn run_gui() -> ExitCode {
cec_revoke,
cec_forget_node,
cec_grants,
cec_viewing,
cec_set_label,
cec_chat_send,
cec_chat_history,
Expand Down
14 changes: 14 additions & 0 deletions gui/src/store.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
cecDeny,
cecForgetNode,
cecGrants,
cecViewing,
cecPending,
cecRevoke,
cecSetLabel,
Expand All @@ -42,6 +43,7 @@ import {
onCecHelp,
onCecRequest,
onCecSession,
onCecViewing,
serviceInstall,
serviceStatus,
serviceStop,
Expand Down Expand Up @@ -146,6 +148,12 @@ class CecStore {
pending = $state<ConnectRequest[]>([]);
/** Live sessions keyed by session id. */
sessions = $state<Record<string, LiveSession>>({});
/** What each technician's live routes actually carry right now (canonical
* tech id → screen/control), pushed by the node on every change. This is
* the truth the "Viewing/Controlling your screen" chip renders: a session
* outlives the console (chat rides it), so session state alone would keep
* the chip lit after the technician closed the console. */
viewing = $state<Record<string, { screen: boolean; control: boolean }>>({});
/** Standing approvals ("who can reach me"). */
grants = $state<Grant[]>([]);
service = $state<ServiceStatus | null>(null);
Expand Down Expand Up @@ -347,6 +355,7 @@ class CecStore {
agent_name: g.agent_name || "A CEC technician",
grant: g,
live: live.find((s) => canonicalTech(s.tech) === key) ?? null,
viewing: this.viewing[key] ?? null,
};
});
for (const s of live) {
Expand All @@ -361,6 +370,7 @@ class CecStore {
agent_name: s.agent_name || "A CEC technician",
grant: null,
live: s,
viewing: this.viewing[key] ?? null,
});
}
return rows;
Expand Down Expand Up @@ -397,6 +407,7 @@ class CecStore {
this.unlisteners.push(await onCecRequest((r) => this.onRequest(r)));
this.unlisteners.push(await onCecSession((s) => this.onSession(s)));
this.unlisteners.push(await onCecGrants((g) => (this.grants = g)));
this.unlisteners.push(await onCecViewing((v) => (this.viewing = v)));
this.unlisteners.push(
await onCecChat((e) => this.appendChat(e.peer, e.message)),
);
Expand Down Expand Up @@ -520,6 +531,9 @@ class CecStore {
this.status = await cecStatus();
this.pending = await cecPending();
this.grants = await cecGrants();
// Pull the live viewing map too, so an app that starts (or reconnects)
// mid-session paints the chip without waiting for the next transition.
this.viewing = await cecViewing();
// The node is the truth for the ask (it withdraws it itself on approval,
// and a restart drops it) — mirror it whenever the status lands, but never
// mid-request: an in-flight ask/cancel (busy) owns the flag, so a status
Expand Down
24 changes: 24 additions & 0 deletions gui/src/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,17 @@ export async function cecGrants(): Promise<Grant[]> {
return Array.isArray(r) ? r : [];
}

/** What each technician's live routes actually carry right now — screen
* (viewing) and control (driving input) — keyed by canonical tech id. This,
* not session state, is what the "Viewing your screen" chip keys on: a
* session outlives the console (chat rides it), so session state over-claims.
* Empty in web mode / on error. */
export type ViewingMap = Record<string, { screen: boolean; control: boolean }>;
export async function cecViewing(): Promise<ViewingMap> {
const r = await tryInvoke<{ techs?: ViewingMap }>("cec_viewing");
return r?.techs ?? {};
}

/** Set this computer's friendly name (shown to the technician on the mesh).
* A convenience beyond the core dial/approve contract; see README. */
export function cecSetLabel(label: string): Promise<null> {
Expand Down Expand Up @@ -333,6 +344,19 @@ export async function onCecGrants(
);
}

/** What technicians' live routes carry changed (console opened/closed, control
* granted route came up/down) — repaint the Viewing/Controlling chip. The
* node's consent sweep pushes the whole map on every transition. */
export async function onCecViewing(
cb: (viewing: ViewingMap) => void,
): Promise<() => void> {
if (!isTauri()) return () => {};
const { listen } = await import("@tauri-apps/api/event");
return listen<{ techs?: ViewingMap }>("cec://viewing", (e) =>
cb(e.payload.techs ?? {}),
);
}

/** A chat line landed on some technician's transcript — a received line, or the
* echo of one we just sent. `peer` is the technician's canonical device id (the
* thread key); `message` is the line. Drives the chat panel live. */
Expand Down
4 changes: 4 additions & 0 deletions gui/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ export interface AccessRow {
grant: Grant | null;
/** The technician's live session right now, or null when not connected. */
live: LiveSession | null;
/** What their live routes actually carry right now — the chip's truth. A
* connected session with `viewing` null/false means chat only (or the
* console was closed): no "Viewing your screen". */
viewing: { screen: boolean; control: boolean } | null;
}

/** The OS background-service status, from the `cec-support-service` crate. */
Expand Down
9 changes: 7 additions & 2 deletions gui/src/ui/AccessList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,14 @@
{#if r.grant && remaining(r.grant)}
<span class="chip warn">{remaining(r.grant)}</span>
{/if}
{#if dot === "live"}
{#if r.viewing?.screen}
<!-- Keyed on what their routes actually carry (the node's
cec://viewing), not on the session: the session outlives
the console (chat rides it), so "connected" alone must
not read as "viewing". Closing the console clears this
within a couple of seconds. -->
<span class="chip ok viewing">
{r.live?.want_control ? "Controlling your screen" : "Viewing your screen"}
{r.viewing.control ? "Controlling your screen" : "Viewing your screen"}
</span>
{/if}
</span>
Expand Down
Loading