@typed-assistant/builder 0.0.57 → 0.0.59
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/pullChanges.tsx +2 -2
- package/src/setupWebserver.tsx +49 -15
- package/src/webserver/App.tsx +54 -17
- package/src/webserver/AppSection.tsx +26 -8
package/package.json
CHANGED
package/src/pullChanges.tsx
CHANGED
|
@@ -7,8 +7,8 @@ export const pullChanges = async () => {
|
|
|
7
7
|
const { stderr, stdout } = await $`git pull`.quiet()
|
|
8
8
|
if (stderr.length > 0) {
|
|
9
9
|
logger.error(
|
|
10
|
-
{ emoji: "⬇️🚨" },
|
|
11
|
-
`Failed to pull changes
|
|
10
|
+
{ additionalDetails: stderr.toString().trim(), emoji: "⬇️🚨" },
|
|
11
|
+
`Failed to pull changes.`,
|
|
12
12
|
)
|
|
13
13
|
return
|
|
14
14
|
}
|
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,48 @@ 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
|
+
data: {
|
|
72
|
+
error?: never
|
|
73
|
+
cpu_percent: number
|
|
74
|
+
memory_usage: number
|
|
75
|
+
memory_limit: number
|
|
76
|
+
memory_percent: number
|
|
77
|
+
max_memory_usage: number
|
|
78
|
+
}
|
|
79
|
+
}>,
|
|
80
|
+
)("/addons/self/stats")
|
|
81
|
+
|
|
82
|
+
if (error) {
|
|
83
|
+
logger.error(
|
|
84
|
+
{ additionalDetails: error.message, emoji: "❌" },
|
|
85
|
+
"Error getting stats",
|
|
86
|
+
)
|
|
87
|
+
} else {
|
|
88
|
+
stats = {
|
|
89
|
+
...data.data,
|
|
90
|
+
max_memory_usage:
|
|
91
|
+
data.data.memory_usage > stats.max_memory_usage
|
|
92
|
+
? data.data.memory_usage
|
|
93
|
+
: stats.max_memory_usage,
|
|
94
|
+
}
|
|
95
|
+
logger.info(
|
|
96
|
+
{ additionalDetails: JSON.stringify(stats, null, 2), emoji: "📊" },
|
|
97
|
+
"Stats updated",
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setTimeout(getStats, 30 * ONE_SECOND)
|
|
102
|
+
}
|
|
60
103
|
|
|
61
104
|
export const startWebappServer = async ({
|
|
62
105
|
basePath,
|
|
@@ -143,20 +186,7 @@ export const startWebappServer = async ({
|
|
|
143
186
|
translations: "HIDDEN",
|
|
144
187
|
}
|
|
145
188
|
})
|
|
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
|
-
})
|
|
189
|
+
.get("/stats", async () => stats)
|
|
160
190
|
.get(
|
|
161
191
|
"/log.txt",
|
|
162
192
|
async ({ query }) => {
|
|
@@ -232,6 +262,8 @@ export const startWebappServer = async ({
|
|
|
232
262
|
}
|
|
233
263
|
})
|
|
234
264
|
|
|
265
|
+
getStats()
|
|
266
|
+
|
|
235
267
|
addKillListener(async () => {
|
|
236
268
|
watcher.close()
|
|
237
269
|
await server.stop()
|
|
@@ -246,7 +278,9 @@ export const startWebappServer = async ({
|
|
|
246
278
|
? { value: undefined }
|
|
247
279
|
: await stderrReader.read()
|
|
248
280
|
|
|
249
|
-
const decodedString =
|
|
281
|
+
const decodedString = stderrValue
|
|
282
|
+
? decoder.decode(stderrValue)
|
|
283
|
+
: decoder.decode(value)
|
|
250
284
|
const convertedMessage = convert.toHtml(decodedString)
|
|
251
285
|
if (convertedMessage !== "") {
|
|
252
286
|
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
|
}
|