@fungi.computer/tourtois 0.2.1 → 0.3.1
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.
- package/dist/OysterComputers.d.ts +9 -4
- package/dist/OysterComputers.js +62 -35
- package/dist/queries.d.ts +1 -0
- package/dist/queries.js +1 -0
- package/package.json +13 -9
|
@@ -2,19 +2,24 @@ import { teamComputerDirectorySchema, type TeamComputer } from "@fungi.computer/
|
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
/** One owner-fleet page returned by the public Computer controls port. */
|
|
4
4
|
export type ComputerPage = z.output<typeof teamComputerDirectorySchema>;
|
|
5
|
-
/**
|
|
5
|
+
/** Host-supplied owner controls over the existing fleet, not a second inventory. */
|
|
6
6
|
export type OysterComputersPort = Readonly<{
|
|
7
7
|
list(offset: number): Promise<ComputerPage>;
|
|
8
|
-
selected(): Promise<string | null>;
|
|
9
8
|
create(name: string | undefined, requestId: string): Promise<TeamComputer>;
|
|
10
9
|
start(computerId: string): Promise<void>;
|
|
11
|
-
select(computerId: string): Promise<TeamComputer>;
|
|
12
10
|
internetAccess(computerId: string): Promise<boolean>;
|
|
13
11
|
setInternetAccess(computerId: string, enabled: boolean): Promise<boolean>;
|
|
12
|
+
/** Only hosts with a persisted default for new Apps supply selection. */
|
|
13
|
+
selection?: Readonly<{
|
|
14
|
+
selected(): Promise<string | null>;
|
|
15
|
+
select(computerId: string): Promise<TeamComputer>;
|
|
16
|
+
}>;
|
|
17
|
+
/** Operator policy readback, when the host exposes it. Unknown locks writes. */
|
|
18
|
+
egressPolicy?(): Promise<"enabled" | "globally-disabled" | "team-disabled">;
|
|
14
19
|
}>;
|
|
15
20
|
/** Bind owner-admitted fleet controls to this trusted shell's host origin. */
|
|
16
21
|
export declare function createOysterComputersPort(origin: URL): OysterComputersPort;
|
|
17
|
-
/** Owner Settings over the host's existing inventory and default
|
|
22
|
+
/** Owner Settings over the host's existing inventory and optional default. */
|
|
18
23
|
export declare function OysterComputers({ teamId, port, onChanged, }: {
|
|
19
24
|
teamId: string;
|
|
20
25
|
port: OysterComputersPort;
|
package/dist/OysterComputers.js
CHANGED
|
@@ -40,11 +40,6 @@ export function createOysterComputersPort(origin) {
|
|
|
40
40
|
const response = await read(`/api/computers?${new URLSearchParams({ limit: "100", offset: String(offset) })}`);
|
|
41
41
|
return teamComputerDirectorySchema.parse(await response.json());
|
|
42
42
|
},
|
|
43
|
-
selected: async () => {
|
|
44
|
-
const response = await read("/api/workbench/directory");
|
|
45
|
-
return (workbenchDirectorySchema.parse(await response.json()).computer
|
|
46
|
-
?.computerId ?? null);
|
|
47
|
-
},
|
|
48
43
|
create: async (name, requestId) => {
|
|
49
44
|
const response = await read("/api/computers", {
|
|
50
45
|
method: "POST",
|
|
@@ -59,12 +54,19 @@ export function createOysterComputersPort(origin) {
|
|
|
59
54
|
});
|
|
60
55
|
await response.json();
|
|
61
56
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
selection: {
|
|
58
|
+
selected: async () => {
|
|
59
|
+
const response = await read("/api/workbench/directory");
|
|
60
|
+
return (workbenchDirectorySchema.parse(await response.json()).computer
|
|
61
|
+
?.computerId ?? null);
|
|
62
|
+
},
|
|
63
|
+
select: async (computerId) => {
|
|
64
|
+
const response = await read("/api/computers/selected", {
|
|
65
|
+
method: "PUT",
|
|
66
|
+
body: JSON.stringify({ computerId }),
|
|
67
|
+
});
|
|
68
|
+
return teamComputerSchema.parse(await response.json());
|
|
69
|
+
},
|
|
68
70
|
},
|
|
69
71
|
internetAccess: async (computerId) => {
|
|
70
72
|
const response = await read(`/api/computers/${encodeURIComponent(computerId)}/internet-access`);
|
|
@@ -76,7 +78,7 @@ export function createOysterComputersPort(origin) {
|
|
|
76
78
|
},
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
|
-
function InternetAccess({ teamId, computer, port, }) {
|
|
81
|
+
function InternetAccess({ teamId, computer, port, policy, }) {
|
|
80
82
|
const queryKey = tourtoisQueryKeys.oysterInternetAccess(teamId, computer.computerId);
|
|
81
83
|
const current = useQuery({
|
|
82
84
|
queryKey,
|
|
@@ -87,13 +89,15 @@ function InternetAccess({ teamId, computer, port, }) {
|
|
|
87
89
|
apply: (_previous, enabled) => enabled,
|
|
88
90
|
mutationFn: (enabled) => port.setInternetAccess(computer.computerId, enabled),
|
|
89
91
|
}));
|
|
90
|
-
return (_jsxs("div", { className: "grid gap-sm", children: [_jsxs("label", { className: "flex gap-sm", children: ["Internet access", _jsx(Switch, { "aria-label": `Internet access for ${computer.name}`, checked: current.data ?? false, disabled:
|
|
92
|
+
return (_jsxs("div", { className: "grid gap-sm", children: [_jsxs("label", { className: "flex gap-sm", children: ["Internet access", _jsx(Switch, { "aria-label": `Internet access for ${computer.name}`, checked: current.data ?? false, disabled: policy !== "enabled" ||
|
|
93
|
+
current.data === undefined ||
|
|
94
|
+
update.isPending, onCheckedChange: (enabled) => {
|
|
91
95
|
update.mutate(enabled);
|
|
92
96
|
} })] }), current.isPending ? (_jsx("p", { role: "status", children: "Checking Internet access\u2026" })) : null, current.isError ? (_jsxs("div", { role: "alert", children: [_jsxs("p", { children: ["Could not check Internet access for ", computer.name, "."] }), _jsx(Button, { onClick: () => {
|
|
93
97
|
void current.refetch();
|
|
94
|
-
}, children: "Retry Internet access" })] })) : null, update.isError ? (_jsxs("p", { role: "alert", children: ["Could not save Internet access for ", computer.name, "."] })) : null] }));
|
|
98
|
+
}, children: "Retry Internet access" })] })) : null, update.isError ? (_jsxs("p", { role: "alert", children: ["Could not save Internet access for ", computer.name, "."] })) : null, policy === "globally-disabled" ? (_jsx("p", { children: "Internet access is locked by the global off switch." })) : null, policy === "team-disabled" ? (_jsx("p", { children: "Internet access is locked for this Team." })) : null] }));
|
|
95
99
|
}
|
|
96
|
-
/** Owner Settings over the host's existing inventory and default
|
|
100
|
+
/** Owner Settings over the host's existing inventory and optional default. */
|
|
97
101
|
export function OysterComputers({ teamId, port, onChanged, }) {
|
|
98
102
|
const client = useQueryClient();
|
|
99
103
|
const [offset, setOffset] = useState(0);
|
|
@@ -101,13 +105,24 @@ export function OysterComputers({ teamId, port, onChanged, }) {
|
|
|
101
105
|
const intent = useRef(null);
|
|
102
106
|
const inventoryKey = tourtoisQueryKeys.oysterComputers(teamId);
|
|
103
107
|
const selectionKey = tourtoisQueryKeys.oysterSelectedComputer(teamId);
|
|
108
|
+
const selection = port.selection;
|
|
104
109
|
const inventory = useQuery({
|
|
105
110
|
queryKey: tourtoisQueryKeys.oysterComputerPage(teamId, offset),
|
|
106
111
|
queryFn: () => port.list(offset),
|
|
107
112
|
});
|
|
108
113
|
const selected = useQuery({
|
|
109
114
|
queryKey: selectionKey,
|
|
110
|
-
|
|
115
|
+
enabled: selection !== undefined,
|
|
116
|
+
queryFn: () => selection === undefined
|
|
117
|
+
? Promise.reject(new Error("This host has no default Computer"))
|
|
118
|
+
: selection.selected(),
|
|
119
|
+
});
|
|
120
|
+
const policy = useQuery({
|
|
121
|
+
queryKey: tourtoisQueryKeys.oysterEgressPolicy(teamId),
|
|
122
|
+
enabled: port.egressPolicy !== undefined,
|
|
123
|
+
queryFn: () => port.egressPolicy === undefined
|
|
124
|
+
? Promise.reject(new Error("This host has no egress policy readback"))
|
|
125
|
+
: port.egressPolicy(),
|
|
111
126
|
});
|
|
112
127
|
const action = useMutation({
|
|
113
128
|
mutationFn: async (input) => {
|
|
@@ -119,7 +134,9 @@ export function OysterComputers({ teamId, port, onChanged, }) {
|
|
|
119
134
|
await port.start(input.computerId);
|
|
120
135
|
return;
|
|
121
136
|
case "select":
|
|
122
|
-
|
|
137
|
+
if (selection === undefined)
|
|
138
|
+
throw new Error("This host has no default Computer");
|
|
139
|
+
await selection.select(input.computerId);
|
|
123
140
|
}
|
|
124
141
|
},
|
|
125
142
|
onSuccess: async (_result, input) => {
|
|
@@ -130,32 +147,42 @@ export function OysterComputers({ teamId, port, onChanged, }) {
|
|
|
130
147
|
}
|
|
131
148
|
await Promise.all([
|
|
132
149
|
client.invalidateQueries({ queryKey: inventoryKey }),
|
|
133
|
-
|
|
150
|
+
...(selection === undefined
|
|
151
|
+
? []
|
|
152
|
+
: [client.invalidateQueries({ queryKey: selectionKey })]),
|
|
134
153
|
]);
|
|
135
154
|
onChanged();
|
|
136
155
|
},
|
|
137
156
|
});
|
|
138
157
|
const unavailable = inventory.isError ||
|
|
139
|
-
selected.isError ||
|
|
158
|
+
(selection !== undefined && selected.isError) ||
|
|
140
159
|
inventory.isFetching ||
|
|
141
|
-
selected.isFetching ||
|
|
160
|
+
(selection !== undefined && selected.isFetching) ||
|
|
142
161
|
action.isPending;
|
|
143
|
-
|
|
162
|
+
const policyStatus = port.egressPolicy === undefined
|
|
163
|
+
? "enabled"
|
|
164
|
+
: (policy.data ?? "unavailable");
|
|
165
|
+
return (_jsxs("section", { className: "grid gap-sm", "aria-label": "Computers", children: [_jsx(SectionHeading, { children: "Computers" }), _jsx("p", { children: selection === undefined
|
|
166
|
+
? "Manage your Team's Computers."
|
|
167
|
+
: "Choose the default Computer used for new App configurations." }), port.egressPolicy !== undefined && policyStatus === "unavailable" ? (_jsxs("div", { role: "alert", children: [_jsx("p", { children: "Internet access policy is unavailable; switches are locked." }), _jsx(Button, { onClick: () => void policy.refetch(), children: "Retry policy" })] })) : null, inventory.isPending ? _jsx("p", { role: "status", children: "Loading Computers\u2026" }) : null, inventory.isError || (selection !== undefined && selected.isError) ? (_jsxs("div", { role: "alert", children: [_jsx("p", { children: "Computer inventory or selection could not be loaded." }), _jsx(Button, { onClick: () => {
|
|
144
168
|
void inventory.refetch();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
if (selection !== undefined)
|
|
170
|
+
void selected.refetch();
|
|
171
|
+
}, children: "Retry" })] })) : null, inventory.isSuccess && inventory.data.computers.length === 0 ? (_jsx("p", { children: "No Computers on this page." })) : null, inventory.data !== undefined ? (_jsx(DataList, { "aria-label": "Computer inventory", children: inventory.data.computers.map((computer) => (_jsx(DataRow, { className: "p-0", children: _jsxs("div", { className: "grid min-w-0 gap-sm", children: [_jsx(ActionRow, { label: computer.name, description: selection !== undefined &&
|
|
172
|
+
selected.isSuccess &&
|
|
173
|
+
selected.data === computer.computerId
|
|
174
|
+
? "Default for new Apps"
|
|
175
|
+
: computer.computerId, trailing: _jsxs("div", { className: "flex flex-wrap gap-sm", children: [_jsx(Button, { "aria-label": `Start ${computer.name}`, disabled: unavailable, onClick: () => {
|
|
176
|
+
action.mutate({
|
|
177
|
+
kind: "start",
|
|
178
|
+
computerId: computer.computerId,
|
|
179
|
+
});
|
|
180
|
+
}, children: "Start" }), selection !== undefined ? (_jsx(Button, { "aria-label": `Use ${computer.name} for new Apps`, disabled: unavailable || selected.data === computer.computerId, onClick: () => {
|
|
181
|
+
action.mutate({
|
|
182
|
+
kind: "select",
|
|
183
|
+
computerId: computer.computerId,
|
|
184
|
+
});
|
|
185
|
+
}, children: "Use for new Apps" })) : null] }) }), _jsx(InternetAccess, { teamId: teamId, computer: computer, port: port, policy: policyStatus })] }) }, computer.computerId))) })) : null, _jsxs("div", { className: "flex gap-sm", "aria-label": "Computer pages", children: [_jsx(Button, { disabled: unavailable || offset === 0, onClick: () => {
|
|
159
186
|
setOffset(Math.max(0, offset - 100));
|
|
160
187
|
}, children: "Previous" }), _jsx(Button, { disabled: unavailable || inventory.data?.nextOffset == null, onClick: () => {
|
|
161
188
|
const next = inventory.data?.nextOffset;
|
package/dist/queries.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const tourtoisQueryKeys: {
|
|
|
4
4
|
oysterComputerPage: (teamId: string, offset: number) => readonly ["tourtois", "oyster-computers", string, number];
|
|
5
5
|
oysterSelectedComputer: (teamId: string) => readonly ["tourtois", "oyster-selected-computer", string];
|
|
6
6
|
oysterInternetAccess: (teamId: string, computerId: string) => readonly ["tourtois", "oyster-internet-access", string, string];
|
|
7
|
+
oysterEgressPolicy: (teamId: string) => readonly ["tourtois", "oyster-egress-policy", string];
|
|
7
8
|
hostSettings: (workspaceId: string) => readonly ["tourtois", "host-settings", string];
|
|
8
9
|
workbench: () => readonly ["tourtois", "workbench"];
|
|
9
10
|
workbenchComputers: (teamId: string) => readonly ["tourtois", "workbench-computers", string];
|
package/dist/queries.js
CHANGED
|
@@ -4,6 +4,7 @@ export const tourtoisQueryKeys = {
|
|
|
4
4
|
oysterComputerPage: (teamId, offset) => [...tourtoisQueryKeys.oysterComputers(teamId), offset],
|
|
5
5
|
oysterSelectedComputer: (teamId) => ["tourtois", "oyster-selected-computer", teamId],
|
|
6
6
|
oysterInternetAccess: (teamId, computerId) => ["tourtois", "oyster-internet-access", teamId, computerId],
|
|
7
|
+
oysterEgressPolicy: (teamId) => ["tourtois", "oyster-egress-policy", teamId],
|
|
7
8
|
hostSettings: (workspaceId) => ["tourtois", "host-settings", workspaceId],
|
|
8
9
|
workbench: () => ["tourtois", "workbench"],
|
|
9
10
|
workbenchComputers: (teamId) => ["tourtois", "workbench-computers", teamId],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/tourtois",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Tourtois: the trusted desktop shell for Oyster and Fungi, with host-supplied Settings sections",
|
|
6
6
|
"type": "module",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"types": "./dist/platform-controls.d.ts",
|
|
35
35
|
"import": "./dist/platform-controls.js"
|
|
36
36
|
},
|
|
37
|
+
"./oyster-computers": {
|
|
38
|
+
"types": "./dist/OysterComputers.d.ts",
|
|
39
|
+
"import": "./dist/OysterComputers.js"
|
|
40
|
+
},
|
|
37
41
|
"./settings-routes": {
|
|
38
42
|
"types": "./dist/settings-routes.d.ts",
|
|
39
43
|
"import": "./dist/settings-routes.js"
|
|
@@ -69,19 +73,19 @@
|
|
|
69
73
|
"LICENSE"
|
|
70
74
|
],
|
|
71
75
|
"dependencies": {
|
|
72
|
-
"@fungi.computer/agar": "0.
|
|
76
|
+
"@fungi.computer/agar": "0.4.0",
|
|
73
77
|
"@fungi.computer/cairn": "0.1.0",
|
|
74
|
-
"@fungi.computer/caps": "0.
|
|
78
|
+
"@fungi.computer/caps": "0.3.1",
|
|
75
79
|
"@fungi.computer/ear-tag": "0.1.0",
|
|
76
80
|
"@fungi.computer/gills": "0.2.0",
|
|
77
81
|
"@fungi.computer/maosaico": "0.1.0",
|
|
78
|
-
"@fungi.computer/parakeet": "0.
|
|
82
|
+
"@fungi.computer/parakeet": "0.4.0",
|
|
79
83
|
"@fungi.computer/puffball": "0.2.0",
|
|
80
|
-
"@fungi.computer/shiitake": "0.
|
|
84
|
+
"@fungi.computer/shiitake": "0.5.0",
|
|
81
85
|
"@fungi.computer/sigils": "0.2.0",
|
|
82
86
|
"@fungi.computer/sprite": "0.2.0",
|
|
83
87
|
"@fungi.computer/sprite-zdog": "0.2.0",
|
|
84
|
-
"@fungi.computer/whistle": "0.
|
|
88
|
+
"@fungi.computer/whistle": "0.2.0",
|
|
85
89
|
"@tanstack/history": "1.162.1",
|
|
86
90
|
"@tanstack/react-form": "1.33.2",
|
|
87
91
|
"@tanstack/react-query": "5.102.8",
|
|
@@ -92,16 +96,16 @@
|
|
|
92
96
|
"lucide-react": "0.460.0",
|
|
93
97
|
"react-dom": "18.3.1",
|
|
94
98
|
"zod": "4.4.3",
|
|
95
|
-
"@fungi.computer/nacre": "0.0.
|
|
99
|
+
"@fungi.computer/nacre": "0.0.3"
|
|
96
100
|
},
|
|
97
101
|
"peerDependencies": {
|
|
98
102
|
"react": "^18.3.1 || ^19.2.0"
|
|
99
103
|
},
|
|
100
104
|
"devDependencies": {
|
|
101
|
-
"@fungi.computer/oyster": "0.
|
|
105
|
+
"@fungi.computer/oyster": "0.5.0",
|
|
102
106
|
"@cloudflare/workers-types": "5.20260828.1",
|
|
103
107
|
"@fungi.computer/forage": "0.2.0",
|
|
104
|
-
"@fungi.computer/mycelium": "0.2.
|
|
108
|
+
"@fungi.computer/mycelium": "0.2.2",
|
|
105
109
|
"@ladle/react": "5.1.1",
|
|
106
110
|
"@types/react": "18.3.3",
|
|
107
111
|
"@vitejs/plugin-react": "6.1.1",
|