Skip to content

Commit bcc9db3

Browse files
authored
1.5.4 (#21)
* Download logs as file * Bumped version to 1.5.4
1 parent ee7c871 commit bcc9db3

File tree

9 files changed

+79
-18
lines changed

9 files changed

+79
-18
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
branches: ["main"]
99

1010
env:
11-
VERSION: "1.5.3"
11+
VERSION: "1.5.4"
1212

1313
jobs:
1414
docker:

pkg/common/common.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package common
22

3-
const Version = "1.5.3"
3+
const Version = "1.5.4"

web/src/app/compose/compose-logs.tsx

+23-6
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,52 @@ import { useEffect, useState } from "react"
1313
import { wsApiBaseUrl } from "@/lib/api-base-url"
1414
import { AttachAddon } from "@xterm/addon-attach"
1515
import { FitAddon } from "@xterm/addon-fit"
16-
import { newTerminal, recreateTerminalElement } from "@/lib/utils"
16+
import {
17+
downloadTerminalTextAsFile,
18+
newTerminal,
19+
recreateTerminalElement,
20+
} from "@/lib/utils"
1721
import useNodeHead from "@/hooks/useNodeHead"
1822
import useNodeComposeItem from "@/hooks/useNodeComposeItem"
23+
import { Button } from "@/components/ui/button"
24+
import { Terminal } from "@xterm/xterm"
1925

2026
export default function ComposeLogs() {
2127
const { nodeId, composeProjectId } = useParams()
2228
const { nodeHead } = useNodeHead(nodeId!)
2329
const { nodeComposeItem } = useNodeComposeItem(nodeId!, composeProjectId!)
2430
const [socket, setSocket] = useState<WebSocket>(null!)
31+
const [terminal, setTerminal] = useState<Terminal>(null!)
2532

2633
useEffect(() => {
27-
const terminal = newTerminal()
34+
const t = newTerminal()
35+
setTerminal(t)
2836

2937
if (socket) socket.close()
3038
const s = new WebSocket(
3139
`${wsApiBaseUrl()}/nodes/${nodeId}/compose/${composeProjectId}/logs`
3240
)
3341
setSocket(s)
3442

35-
terminal.loadAddon(new AttachAddon(s))
43+
t.loadAddon(new AttachAddon(s))
3644
const fitAddon = new FitAddon()
37-
terminal.loadAddon(fitAddon)
45+
t.loadAddon(fitAddon)
3846

3947
const terminalEl = recreateTerminalElement("terminalContainer", "terminal")
40-
terminal.open(terminalEl!)
48+
t.open(terminalEl!)
4149
fitAddon.fit()
4250
addEventListener("resize", () => {
4351
fitAddon?.fit()
4452
})
4553
}, [composeProjectId])
4654

55+
const handleDownload = () => {
56+
downloadTerminalTextAsFile(
57+
terminal,
58+
`logs_${nodeComposeItem?.projectName}.txt`
59+
)
60+
}
61+
4762
return (
4863
<MainArea>
4964
<TopBar>
@@ -65,7 +80,9 @@ export default function ComposeLogs() {
6580
<BreadcrumbSeparator />
6681
<BreadcrumbCurrent>Logs</BreadcrumbCurrent>
6782
</Breadcrumb>
68-
<TopBarActions></TopBarActions>
83+
<TopBarActions>
84+
<Button onClick={handleDownload}>Download to File</Button>
85+
</TopBarActions>
6986
</TopBar>
7087
<MainContent>
7188
<div id="terminalContainer">

web/src/app/containers/container-logs.tsx

+20-6
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,48 @@ import { useParams } from "react-router-dom"
1313
import "/node_modules/xterm/css/xterm.css"
1414
import { useEffect, useState } from "react"
1515
import { wsApiBaseUrl } from "@/lib/api-base-url"
16-
import { newTerminal, recreateTerminalElement } from "@/lib/utils"
16+
import {
17+
downloadTerminalTextAsFile,
18+
newTerminal,
19+
recreateTerminalElement,
20+
} from "@/lib/utils"
1721
import { AttachAddon } from "@xterm/addon-attach"
1822
import useNodeHead from "@/hooks/useNodeHead"
23+
import { Button } from "@/components/ui/button"
24+
import { Terminal } from "@xterm/xterm"
1925

2026
export default function ContainerLogs() {
2127
const { nodeId, containerId } = useParams()
2228
const { nodeHead } = useNodeHead(nodeId!)
2329
const [socket, setSocket] = useState<WebSocket>(null!)
30+
const [terminal, setTerminal] = useState<Terminal>(null!)
2431

2532
useEffect(() => {
26-
const terminal = newTerminal()
33+
const t = newTerminal()
34+
setTerminal(t)
2735

2836
if (socket) socket.close()
2937
const s = new WebSocket(
3038
`${wsApiBaseUrl()}/nodes/${nodeId}/containers/${containerId}/logs`
3139
)
3240
setSocket(s)
3341

34-
terminal.loadAddon(new AttachAddon(s))
42+
t.loadAddon(new AttachAddon(s))
3543
const fitAddon = new FitAddon()
36-
terminal.loadAddon(fitAddon)
44+
t.loadAddon(fitAddon)
3745

3846
const terminalEl = recreateTerminalElement("terminalContainer", "terminal")
39-
terminal.open(terminalEl!)
47+
t.open(terminalEl!)
4048
fitAddon.fit()
4149
addEventListener("resize", () => {
4250
fitAddon?.fit()
4351
})
4452
}, [containerId])
4553

54+
const handleDownload = () => {
55+
downloadTerminalTextAsFile(terminal, `logs_${containerId}.txt`)
56+
}
57+
4658
return (
4759
<MainArea>
4860
<TopBar>
@@ -59,7 +71,9 @@ export default function ContainerLogs() {
5971
Logs for <span className="font-semibold">{containerId}</span>
6072
</BreadcrumbCurrent>
6173
</Breadcrumb>
62-
<TopBarActions></TopBarActions>
74+
<TopBarActions>
75+
<Button onClick={handleDownload}>Download to File</Button>
76+
</TopBarActions>
6377
</TopBar>
6478
<MainContent>
6579
<div id="terminalContainer">

web/src/components/side-nav/side-nav-compose.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function SideNavCompose() {
3535
<SideNavBack to={`/nodes/${nodeId}/compose`} />
3636
<ul role="list" className="-mx-2 space-y-1">
3737
{items.map((item) => (
38-
<li>
38+
<li key={item.title}>
3939
<SideBarItem to={item.link}>
4040
{item.icon}
4141
{item.title}

web/src/components/side-nav/side-nav-node.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function SideNavNode() {
5757
<SideNavBack to="/nodes" />
5858
<ul role="list" className="-mx-2 space-y-1">
5959
{items.map((item) => (
60-
<li>
60+
<li key={item.title}>
6161
<SideBarItem to={item.link}>
6262
{item.icon}
6363
{item.title}

web/src/components/side-nav/side-nav-top-level.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function SideNavTopLevel() {
4444
<>
4545
<ul role="list" className="-mx-2 space-y-1">
4646
{items.map((item) => (
47-
<li>
47+
<li key={item.title}>
4848
<SideBarItem to={item.link}>
4949
{item.icon}
5050
{item.title}

web/src/lib/utils.ts

+30
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ export function newTerminal(convertToEol?: boolean) {
8989
})
9090
}
9191

92+
export function downloadTerminalTextAsFile(
93+
terminal: Terminal,
94+
filename: string
95+
) {
96+
let text = ""
97+
const l = terminal.buffer.normal.length
98+
for (let i = 0; i < l; i++) {
99+
const line = terminal.buffer.normal.getLine(i)?.translateToString(true)
100+
text += `${line}\r\n`
101+
}
102+
103+
download(filename, text)
104+
}
105+
92106
export function initMonaco() {
93107
loader.init().then((monaco) => {
94108
monaco.editor.defineTheme("dark", {
@@ -129,3 +143,19 @@ export function toastFailed(message: string) {
129143
description: message,
130144
})
131145
}
146+
147+
export function download(filename: string, text: string) {
148+
var element = document.createElement("a")
149+
element.setAttribute(
150+
"href",
151+
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
152+
)
153+
element.setAttribute("download", filename)
154+
155+
element.style.display = "none"
156+
document.body.appendChild(element)
157+
158+
element.click()
159+
160+
document.body.removeChild(element)
161+
}

web/src/lib/version.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const VERSION = "1.5.3"
1+
export const VERSION = "1.5.4"

0 commit comments

Comments
 (0)