A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: hopefully SSE on Safari

Brooke f1b7e093 c8559d72

+55 -31
+1 -1
packages/panel/package.json
··· 36 36 "@xterm/addon-web-links": "^0.12.0", 37 37 "@xterm/xterm": "^6.0.0", 38 38 "codemirror-json-schema": "^0.8.1", 39 + "eventsource-parser": "^3.0.6", 39 40 "hotkeys-js": "^4.0.2", 40 41 "melt": "^0.44.0", 41 42 "openapi-fetch": "^0.17.0", 42 43 "openapi-typescript": "^7.13.0", 43 - "parse-sse": "^0.1.0", 44 44 "prettier": "^3.4.2", 45 45 "prettier-plugin-svelte": "^3.3.3", 46 46 "roving-ux": "^1.0.5",
+14 -6
packages/panel/src/lib/api/realtime.svelte.ts
··· 1 - import { parseServerSentEvents, type ServerSentEvent } from "parse-sse"; 1 + import { EventSourceParserStream } from "eventsource-parser/stream"; 2 + import type { EventSourceMessage } from "eventsource-parser"; 2 3 import type { components } from "./openapi"; 3 4 import { Backoff, error, warn } from "$lib"; 4 5 import { client, isAuthenticated } from "."; ··· 51 52 fetch, 52 53 }); 53 54 55 + if (!response.body) throw new Error("Missing response body"); 56 + 54 57 backoff.reset(); 55 58 56 59 try { 57 - for await (const event of parseServerSentEvents( 58 - response, 59 - ) as unknown as AsyncIterable<ServerSentEvent>) { 60 + const events = response.body 61 + .pipeThrough(new TextDecoderStream()) 62 + .pipeThrough(new EventSourceParserStream()); 63 + const reader = events.getReader(); 64 + 65 + while (true) { 66 + const { done, value: event } = await reader.read(); 67 + if (done) break; 60 68 if (signal.aborted) return; 61 69 handleEvent(event); 62 70 } ··· 78 86 * Handle incoming server-sent events from the server. 79 87 * @param event The event received from the server. 80 88 */ 81 - function handleEvent(event: ServerSentEvent) { 82 - switch (event.type) { 89 + function handleEvent(event: EventSourceMessage) { 90 + switch (event.event) { 83 91 case "list": 84 92 patch(list, JSON.parse(event.data)); 85 93 case "log":
+30 -15
packages/panel/src/lib/component/LogTerminal.svelte
··· 1 1 <script lang="ts"> 2 - import { parseServerSentEvents, type ServerSentEvent } from "parse-sse"; 2 + import { EventSourceParserStream } from "eventsource-parser/stream"; 3 + import type { EventSourceMessage } from "eventsource-parser"; 3 4 import { WebLinksAddon } from "@xterm/addon-web-links"; 4 5 import type { Attachment } from "svelte/attachments"; 5 6 import { Terminal, type ITheme } from "@xterm/xterm"; ··· 70 71 signal, 71 72 }); 72 73 74 + if (!response.body) throw new Error("Missing response body"); 75 + 73 76 backoff.reset(); 74 77 75 - for await (const event of parseServerSentEvents( 76 - response, 77 - ) as unknown as AsyncIterable<ServerSentEvent>) { 78 + const events = response.body 79 + .pipeThrough(new TextDecoderStream()) 80 + .pipeThrough(new EventSourceParserStream()); 81 + const reader = events.getReader(); 82 + 83 + while (true) { 84 + const { done, value: event } = await reader.read(); 85 + if (done) break; 78 86 if (signal.aborted) return; 79 87 loading = false; 80 88 81 - if (event.type === "close") { 82 - const terminal = terminals.get(event.data); 83 - if (terminal) terminal.dispose(); 84 - terminals.delete(event.data); 85 - } else { 86 - const id = event.type; 87 - if (!terminals.has(id)) terminals.set(id, new Terminal({ theme, disableStdin: true })); 88 - const terminal = terminals.get(event.type)!; 89 - 90 - terminal.write(Uint8Array.from(atob(event.data), (c) => c.charCodeAt(0))); 91 - } 89 + handleEvent(event); 92 90 } 93 91 } catch (e) { 94 92 if (signal.aborted) return; 95 93 await backoff.wait(); 96 94 } 95 + } 96 + } 97 + 98 + function handleEvent(event: EventSourceMessage) { 99 + const eventType = event.event; 100 + if (!eventType) return; 101 + 102 + if (eventType === "close") { 103 + const terminal = terminals.get(event.data); 104 + if (terminal) terminal.dispose(); 105 + terminals.delete(event.data); 106 + } else { 107 + const id = eventType; 108 + if (!terminals.has(id)) terminals.set(id, new Terminal({ theme, disableStdin: true })); 109 + const terminal = terminals.get(id)!; 110 + 111 + terminal.write(Uint8Array.from(atob(event.data), (c) => c.charCodeAt(0))); 97 112 } 98 113 } 99 114
+1
packages/panel/vite.config.ts
··· 14 14 changeOrigin: true, 15 15 }, 16 16 }, 17 + allowedHosts: true, 17 18 }, 18 19 });
+9 -9
pnpm-lock.yaml
··· 79 79 codemirror-json-schema: 80 80 specifier: ^0.8.1 81 81 version: 0.8.1(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1) 82 + eventsource-parser: 83 + specifier: ^3.0.6 84 + version: 3.0.6 82 85 hotkeys-js: 83 86 specifier: ^4.0.2 84 87 version: 4.0.2 ··· 91 94 openapi-typescript: 92 95 specifier: ^7.13.0 93 96 version: 7.13.0(typescript@5.9.3) 94 - parse-sse: 95 - specifier: ^0.1.0 96 - version: 0.1.0 97 97 prettier: 98 98 specifier: ^3.4.2 99 99 version: 3.8.1 ··· 909 909 esrap@2.2.3: 910 910 resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==} 911 911 912 + eventsource-parser@3.0.6: 913 + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} 914 + engines: {node: '>=18.0.0'} 915 + 912 916 fast-copy@3.0.2: 913 917 resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} 914 918 ··· 1117 1121 parse-json@8.3.0: 1118 1122 resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} 1119 1123 engines: {node: '>=18'} 1120 - 1121 - parse-sse@0.1.0: 1122 - resolution: {integrity: sha512-8bObUwtEuLp2Z1gP6iRVMBzmEqaU5ohmofa3WmZVFfFZFKRP/+c03y8NVYzzXmQMotQ6mDS5Mnyk6tT1gKOTcQ==} 1123 - engines: {node: '>=20'} 1124 1124 1125 1125 picocolors@1.1.1: 1126 1126 resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} ··· 2096 2096 dependencies: 2097 2097 '@jridgewell/sourcemap-codec': 1.5.5 2098 2098 2099 + eventsource-parser@3.0.6: {} 2100 + 2099 2101 fast-copy@3.0.2: {} 2100 2102 2101 2103 fast-deep-equal@3.1.3: {} ··· 2329 2331 '@babel/code-frame': 7.29.0 2330 2332 index-to-position: 1.2.0 2331 2333 type-fest: 4.41.0 2332 - 2333 - parse-sse@0.1.0: {} 2334 2334 2335 2335 picocolors@1.1.1: {} 2336 2336