@typed-assistant/builder 0.0.57 → 0.0.58
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/package.json +1 -1
- package/src/setupWebserver.tsx +47 -15
- package/src/webserver/App.tsx +54 -17
- package/src/webserver/AppSection.tsx +26 -8
package/package.json
CHANGED
package/src/setupWebserver.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { restartAddon } from "./restartAddon"
|
|
|
13
13
|
import { levels } from "@typed-assistant/logger/levels"
|
|
14
14
|
import { getSupervisorAPI } from "@typed-assistant/utils/getHassAPI"
|
|
15
15
|
import { withErrorHandling } from "@typed-assistant/utils/withErrorHandling"
|
|
16
|
+
import { ONE_SECOND } from "@typed-assistant/utils/durations"
|
|
16
17
|
|
|
17
18
|
const indexHtmlFilePath = `${import.meta.dir}/webserver/index.html` as const
|
|
18
19
|
const cssFile = `${import.meta.dir}/webserver/input.css` as const
|
|
@@ -57,6 +58,46 @@ const subscribers = new Map<string, (message: string) => void>()
|
|
|
57
58
|
const logSubscribers = new Map<string, () => void>()
|
|
58
59
|
|
|
59
60
|
let lastMessage = ""
|
|
61
|
+
let stats = {
|
|
62
|
+
cpu_percent: null as number | null,
|
|
63
|
+
memory_usage: null as number | null,
|
|
64
|
+
memory_limit: null as number | null,
|
|
65
|
+
memory_percent: null as number | null,
|
|
66
|
+
max_memory_usage: 0,
|
|
67
|
+
}
|
|
68
|
+
const getStats = async () => {
|
|
69
|
+
const { data, error } = await withErrorHandling(
|
|
70
|
+
getSupervisorAPI<{
|
|
71
|
+
error?: never
|
|
72
|
+
cpu_percent: number
|
|
73
|
+
memory_usage: number
|
|
74
|
+
memory_limit: number
|
|
75
|
+
memory_percent: number
|
|
76
|
+
max_memory_usage: number
|
|
77
|
+
}>,
|
|
78
|
+
)("/addons/self/stats")
|
|
79
|
+
|
|
80
|
+
if (error) {
|
|
81
|
+
logger.error(
|
|
82
|
+
{ additionalDetails: error.message, emoji: "❌" },
|
|
83
|
+
"Error getting stats",
|
|
84
|
+
)
|
|
85
|
+
} else {
|
|
86
|
+
stats = {
|
|
87
|
+
...data,
|
|
88
|
+
max_memory_usage:
|
|
89
|
+
data.memory_usage > stats.max_memory_usage
|
|
90
|
+
? data.memory_usage
|
|
91
|
+
: stats.max_memory_usage,
|
|
92
|
+
}
|
|
93
|
+
logger.info(
|
|
94
|
+
{ additionalDetails: JSON.stringify(stats, null, 2), emoji: "📊" },
|
|
95
|
+
"Stats updated",
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setTimeout(getStats, 30 * ONE_SECOND)
|
|
100
|
+
}
|
|
60
101
|
|
|
61
102
|
export const startWebappServer = async ({
|
|
62
103
|
basePath,
|
|
@@ -143,20 +184,7 @@ export const startWebappServer = async ({
|
|
|
143
184
|
translations: "HIDDEN",
|
|
144
185
|
}
|
|
145
186
|
})
|
|
146
|
-
.get("/stats", async () =>
|
|
147
|
-
const { data, error } = await withErrorHandling(
|
|
148
|
-
getSupervisorAPI<{
|
|
149
|
-
error?: never
|
|
150
|
-
cpu_percent: number
|
|
151
|
-
memory_usage: number
|
|
152
|
-
memory_limit: number
|
|
153
|
-
memory_percent: number
|
|
154
|
-
}>,
|
|
155
|
-
)("/addons/self/stats")
|
|
156
|
-
|
|
157
|
-
if (error) return { error: error.message }
|
|
158
|
-
return data
|
|
159
|
-
})
|
|
187
|
+
.get("/stats", async () => stats)
|
|
160
188
|
.get(
|
|
161
189
|
"/log.txt",
|
|
162
190
|
async ({ query }) => {
|
|
@@ -232,6 +260,8 @@ export const startWebappServer = async ({
|
|
|
232
260
|
}
|
|
233
261
|
})
|
|
234
262
|
|
|
263
|
+
getStats()
|
|
264
|
+
|
|
235
265
|
addKillListener(async () => {
|
|
236
266
|
watcher.close()
|
|
237
267
|
await server.stop()
|
|
@@ -246,7 +276,9 @@ export const startWebappServer = async ({
|
|
|
246
276
|
? { value: undefined }
|
|
247
277
|
: await stderrReader.read()
|
|
248
278
|
|
|
249
|
-
const decodedString =
|
|
279
|
+
const decodedString = stderrValue
|
|
280
|
+
? decoder.decode(stderrValue)
|
|
281
|
+
: decoder.decode(value)
|
|
250
282
|
const convertedMessage = convert.toHtml(decodedString)
|
|
251
283
|
if (convertedMessage !== "") {
|
|
252
284
|
lastMessage = convertedMessage
|
package/src/webserver/App.tsx
CHANGED
|
@@ -3,6 +3,9 @@ import { Logs } from "./Logs"
|
|
|
3
3
|
import { useEffect, useState } from "react"
|
|
4
4
|
import { app } from "./api"
|
|
5
5
|
import { Await } from "ts-toolbelt/out/Any/Await"
|
|
6
|
+
import { AppSection } from "./AppSection"
|
|
7
|
+
import { set } from "zod"
|
|
8
|
+
import { ONE_SECOND } from "@typed-assistant/utils/durations"
|
|
6
9
|
|
|
7
10
|
const basePath = process.env.BASE_PATH ?? ""
|
|
8
11
|
|
|
@@ -13,43 +16,77 @@ const App = () => {
|
|
|
13
16
|
<Terminal />
|
|
14
17
|
</div>
|
|
15
18
|
<div className="col-span-1">
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
<Logs basePath={basePath} />
|
|
19
|
-
</div>
|
|
19
|
+
<Stats />
|
|
20
|
+
<Logs basePath={basePath} />
|
|
20
21
|
</div>
|
|
21
22
|
</div>
|
|
22
23
|
)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const Stats = () => {
|
|
27
|
+
const [counter, setCounter] = useState(0)
|
|
26
28
|
const [error, setError] = useState<string | null>(null)
|
|
29
|
+
const [lastUpdated, setLastUpdated] = useState<number | null>(null)
|
|
27
30
|
const [stats, setStats] =
|
|
28
|
-
useState<Awaited<ReturnType<typeof app.stats.get>>["data"]>()
|
|
31
|
+
useState<Awaited<ReturnType<typeof app.stats.get>>["data"]>(null)
|
|
29
32
|
|
|
30
33
|
useEffect(() => {
|
|
31
34
|
app.stats.get().then((stats) => {
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
setLastUpdated(Date.now())
|
|
36
|
+
if (stats.error) {
|
|
37
|
+
setError(stats.error.message)
|
|
34
38
|
return
|
|
35
39
|
}
|
|
36
|
-
|
|
37
40
|
setStats(stats.data)
|
|
41
|
+
|
|
42
|
+
const timeout = setTimeout(() => {
|
|
43
|
+
setCounter((c) => c + 1)
|
|
44
|
+
}, 30 * ONE_SECOND)
|
|
45
|
+
return () => clearTimeout(timeout)
|
|
38
46
|
})
|
|
39
|
-
}, [])
|
|
47
|
+
}, [counter])
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
return (
|
|
50
|
+
<AppSection className="pb-0" fullHeight={false} scrollable={false}>
|
|
51
|
+
{error ? (
|
|
52
|
+
<>Error: {error}</>
|
|
53
|
+
) : stats ? (
|
|
54
|
+
<div
|
|
55
|
+
title={`Last updated: ${lastUpdated ? new Date(lastUpdated).toLocaleTimeString() : "Never"}`}
|
|
56
|
+
>
|
|
57
|
+
<div className="mb-3">
|
|
58
|
+
<div className="mb-1">
|
|
59
|
+
Memory:{" "}
|
|
60
|
+
{stats.memory_usage
|
|
61
|
+
? `${bytesToMegaBytes(stats.memory_usage ?? 0)} / ${bytesToMegaBytes(stats.memory_limit ?? 0)}MB`
|
|
62
|
+
: "Loading..."}
|
|
63
|
+
</div>
|
|
64
|
+
<ProgressBar value={stats.memory_percent ?? 0} />
|
|
65
|
+
</div>
|
|
66
|
+
<div className="mb-1">
|
|
67
|
+
CPU: {stats.cpu_percent ? `${stats.cpu_percent}%` : "Loading..."}
|
|
68
|
+
</div>
|
|
69
|
+
<ProgressBar value={stats.cpu_percent ?? 0} />
|
|
70
|
+
</div>
|
|
71
|
+
) : (
|
|
72
|
+
<div>Loading...</div>
|
|
73
|
+
)}
|
|
74
|
+
</AppSection>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
47
77
|
|
|
78
|
+
const ProgressBar = ({ value }: { value: number }) => {
|
|
48
79
|
return (
|
|
49
|
-
<div className="">
|
|
50
|
-
<div
|
|
80
|
+
<div className="relative w-full h-2 bg-slate-800 rounded-md overflow-hidden">
|
|
81
|
+
<div
|
|
82
|
+
className="absolute h-full bg-slate-600"
|
|
83
|
+
style={{ width: `${value}%` }}
|
|
84
|
+
></div>
|
|
51
85
|
</div>
|
|
52
86
|
)
|
|
53
87
|
}
|
|
54
88
|
|
|
89
|
+
const bytesToMegaBytes = (bytes: number) =>
|
|
90
|
+
Math.round((bytes / 1024 / 1024) * 100) / 100
|
|
91
|
+
|
|
55
92
|
export default App
|
|
@@ -1,16 +1,34 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { twMerge } from "tailwind-merge"
|
|
3
|
+
|
|
1
4
|
export const AppSection = ({
|
|
2
|
-
renderHeader,
|
|
3
5
|
children,
|
|
6
|
+
className,
|
|
7
|
+
fullHeight = true,
|
|
8
|
+
renderHeader,
|
|
9
|
+
scrollable = true,
|
|
4
10
|
}: {
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
children: ReactNode
|
|
12
|
+
className?: string
|
|
13
|
+
fullHeight?: boolean
|
|
14
|
+
renderHeader?: () => JSX.Element
|
|
15
|
+
scrollable?: boolean
|
|
7
16
|
}) => {
|
|
8
17
|
return (
|
|
9
|
-
<div
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
<div
|
|
19
|
+
className={twMerge(
|
|
20
|
+
"p-4 text-xs max-h-dvh w-dvw md:w-auto flex flex-col",
|
|
21
|
+
fullHeight ? "h-full" : "",
|
|
22
|
+
scrollable ? "overflow-x-auto" : "",
|
|
23
|
+
className ?? "",
|
|
24
|
+
)}
|
|
25
|
+
>
|
|
26
|
+
{renderHeader ? (
|
|
27
|
+
<div className="flex flex-wrap gap-4 mb-4 items-center justify-between">
|
|
28
|
+
{renderHeader()}
|
|
29
|
+
</div>
|
|
30
|
+
) : null}
|
|
31
|
+
<div className={scrollable ? "overflow-x-auto" : ""}>{children}</div>
|
|
14
32
|
</div>
|
|
15
33
|
)
|
|
16
34
|
}
|