Skip to content

Commit

Permalink
feat: Added troubleshoot menu options in desktop
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Kosiewski <[email protected]>
  • Loading branch information
Thomas Kosiewski authored and pascalbreuninger committed Dec 4, 2024
1 parent fba226c commit 77b2ad4
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 31 deletions.
35 changes: 22 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

1. Clone the repository locally
2. If you want to change something in DevPod agent code:
1. Exchange the URL in [DefaultAgentDownloadURL](./pkg/agent/agent.go) with a custom public repository release you have created.
1. Exchange the URL in [DefaultAgentDownloadURL](./pkg/agent/agent.go) with a
custom public repository release you have created.
2. Build devpod via: `./hack/rebuild.sh`
3. Upload `test/devpod-linux-amd64` and `test/devpod-linux-arm64` to the public repository release assets.
3. Upload `test/devpod-linux-amd64` and `test/devpod-linux-arm64` to the public
repository release assets.
3. Build devpod via: `./hack/rebuild.sh` (asking for sudo password)
4. Add docker provider via `devpod provider add docker`
5. Configure docker provider via `devpod use provider docker`
Expand All @@ -15,32 +17,39 @@
## Build from source

Prerequisites CLI:

- [Go 1.20](https://go.dev/doc/install)

Once installed, run
Once installed, run
`CGO_ENABLED=0 go build -ldflags "-s -w" -o devpod-cli`

Prerequisites GUI:

- [NodeJS + yarn](https://nodejs.org/en/)
- [Rust](https://www.rust-lang.org/tools/install)
- [Go](https://go.dev/doc/install)

To build the app on Linux, you will need the following dependencies:

```console
sudo apt-get install libappindicator3-1 libgdk-pixbuf2.0-0 libbsd0 libxdmcp6 libwmf-0.2-7 libwmf-0.2-7-gtk libgtk-3-0 libwmf-dev libwebkit2gtk-4.0-37 librust-openssl-sys-dev librust-glib-sys-dev
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev
```bash
sudo apt-get install libappindicator3-1 libgdk-pixbuf2.0-0 libbsd0 libxdmcp6 \
libwmf-0.2-7 libwmf-0.2-7-gtk libgtk-3-0 libwmf-dev libwebkit2gtk-4.0-37 \
librust-openssl-sys-dev librust-glib-sys-dev
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev \
libayatana-appindicator3-dev librsvg2-dev
```

Once installed, run

- `cd desktop`
- `yarn tauri build --config src-tauri/tauri-dev.conf.json`

The application should be in `desktop/src-tauri/target/release`

## Provider

Head over to [the docs](https://devpod.sh/docs/developing-providers/quickstart) for an introduction into developing your own providers
Head over to [the docs](https://devpod.sh/docs/developing-providers/quickstart)
for an introduction into developing your own providers

### Publish your provider

Expand All @@ -51,18 +60,18 @@ Once you're provider is ready, update

to get your provider featured both in the documentation and the UI


## Deeplinks

DevPod Desktop can handle deep links to perform various actions, like opening or importing workspaces.
DevPod Desktop can handle deep links to perform various actions, like opening or
importing workspaces.
The scheme is:

protocol: `devpod://`
host: `command`
searchParams: `foo=bar&fizz=buzz`

resulting in a full url string of `devpod://command?foo=bar&fizz=buzz`. For more information, take a look at the indvidual command sections below.

resulting in a full url string of `devpod://command?foo=bar&fizz=buzz`. For more
information, take a look at the indvidual command sections below.

### Open Workspace

Expand All @@ -73,10 +82,10 @@ searchParams: `source` (required), `workspace`, `provider`, `ide`

`devpod://open?source=your-url-encoded-source&workspace=my-workspace&provider=docker&ide=vscode`


### Import Workspace

Import a remote DevPod.Pro workspace into your local client

host: `import`
searchParams: `workspace_id` (required), `workspace_uid` (required), `devpod_pro_host` (required), `options`
searchParams: `workspace_id` (required), `workspace_uid` (required),
`devpod_pro_host` (required), `options`
46 changes: 32 additions & 14 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
## Development

1. Install [NodeJS](https://nodejs.org/en/)
2. Install [Yarn](https://yarnpkg.com/getting-started/install) and make sure you use yarn v1, by running `yarn set version 1.22.1`
2. Install [Yarn](https://yarnpkg.com/getting-started/install) and make sure you
use yarn v1, by running `yarn set version 1.22.1`
3. Install [Rust](https://www.rust-lang.org/tools/install)
4. Install [Go](https://go.dev/doc/install)
5. Run `./hack/rebuild.sh` from the root directory of the repo
Expand All @@ -16,40 +17,57 @@

To build the app on Linux, you will need the following dependencies:

```console
sudo apt-get install libappindicator3-1 libgdk-pixbuf2.0-0 libbsd0 libxdmcp6 libwmf-0.2-7 libwmf-0.2-7-gtk libgtk-3-0 libwmf-dev libwebkit2gtk-4.0-37 librust-openssl-sys-dev librust-glib-sys-dev
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev file build-essential
```bash
sudo apt-get install libappindicator3-1 libgdk-pixbuf2.0-0 libbsd0 libxdmcp6 \
libwmf-0.2-7 libwmf-0.2-7-gtk libgtk-3-0 libwmf-dev libwebkit2gtk-4.0-37 \
librust-openssl-sys-dev librust-glib-sys-dev
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev \
libayatana-appindicator3-dev librsvg2-dev file build-essential
```

### Additional Information

Make sure all of your dependencies are installed and up to date by running `yarn` and `cd src-tauri && cargo update`.
Make sure all of your dependencies are installed and up to date by running `yarn`
and `cd src-tauri && cargo update`.

Frontend code lives in `src`
Backend code lives in `src-tauri`

Entrypoint for the application is the `main` function in `src-tauri/main.rs`. It instructs tauri to set up the application, bootstrap the webview and serve our static assets.
As of now, we just bundle all of the javascript into one file, so we don't have any prerendering or code splitting going on.
Entrypoint for the application is the `main` function in `src-tauri/main.rs`.
It instructs tauri to set up the application, bootstrap the webview and serve our
static assets. As of now, we just bundle all of the javascript into one file, so
we don't have any prerendering or code splitting going on.

To spin up the application in development mode, run `yarn tauri dev`. It will report both the frontend webserver output (vite) and the backend logs to your current terminal.
Tauri should automatically restart the app if your backend code changes and vite is responsible for hot module updates in the frontend.
To spin up the application in development mode, run `yarn tauri dev`. It will
report both the frontend webserver output (vite) and the backend logs to your
current terminal.
Tauri should automatically restart the app if your backend code changes and vite
is responsible for hot module updates in the frontend.
Enable debug logging to stdout during development with `DEBUG=true yarn tauri dev`.

If you just want to preview the project locally, make sure to disabled the auto update feature by setting `desktop/src-tauri/tauri.conf.json->updater.active=false`. Please be careful not to commit this change later on.
Once you're happy with the current state, give it a spin in release mode by running `yarn tauri build`. You can find the packaged version of the application in the `src-tauri/target/release/{PLATFORM}` folder.
If you just want to preview the project locally, make sure to disabled the auto
update feature by setting `desktop/src-tauri/tauri.conf.json->updater.active=false`.
Please be careful not to commit this change later on.
Once you're happy with the current state, give it a spin in release mode by running
`yarn tauri build`. You can find the packaged version of the application in the
`src-tauri/target/release/{PLATFORM}` folder.

## Check Type Errors

Run `yarn types:check` to check for errors

## Versioning

The apps version is determined by the one in `package.json`. Be careful not to add one in `tauri.conf.json` as it override the current one.
The apps version is determined by the one in `package.json`. Be careful not to add
one in `tauri.conf.json` as it override the current one.
You can upgrade the version manually or run `yarn version ...`

## Build desktop app

If your development environment is setup successfully and you're able to run `yarn desktop:dev` without problems, you also should be able to build the app locally by runnning `yarn desktop:build:dev`.
Notice the `:dev` suffix, if you omit this it'll try to build with the updater enabled. This won't work on your machine as it requires sensitive information.
If your development environment is setup successfully and you're able to run
`yarn desktop:dev` without problems, you also should be able to build the app
locally by runnning `yarn desktop:build:dev`.
Notice the `:dev` suffix, if you omit this it'll try to build with the updater
enabled. This won't work on your machine as it requires sensitive information.

The output of the command can be found in `desktop/src-tauri/target/release/bundle`.
3 changes: 2 additions & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,6 @@
},
"resolutions": {
"lodash": "4.17.21"
}
},
"packageManager": "[email protected]"
}
12 changes: 10 additions & 2 deletions desktop/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ class Client {
}
}

public async selectFromDir(): Promise<string | string[] | null> {
return dialog.open({ directory: true, multiple: false })
public async selectFromDir(title?: string): Promise<string | null> {
return dialog.open({ title, directory: true, multiple: false })
}

public async selectFromFileYaml(): Promise<string | string[] | null> {
Expand All @@ -225,6 +225,14 @@ class Client {
return fs.copyFile(src, dest)
}

public async copyFilePaths(src: string[], dest: string[]) {
return this.copyFile(await path.join(...src), await path.join(...dest))
}

public async writeTextFile(targetPath: string[], data: string) {
return fs.writeTextFile(await path.join(...targetPath), data)
}

public async installCLI(force: boolean = false): Promise<Result<void>> {
try {
await invoke("install_cli", { force })
Expand Down
1 change: 1 addition & 0 deletions desktop/src/client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const DEVPOD_COMMAND_GET_WORKSPACE_CONFIG = "get-workspace-config"
export const DEVPOD_COMMAND_GET_PROVIDER_NAME = "get-provider-name"
export const DEVPOD_COMMAND_GET_PRO_NAME = "get-pro-name"
export const DEVPOD_COMMAND_CHECK_PROVIDER_UPDATE = "check-provider-update"
export const DEVPOD_COMMAND_TROUBLESHOOT = "troubleshoot"
export const DEVPOD_FLAG_JSON_LOG_OUTPUT = "--log-output=json"
export const DEVPOD_FLAG_JSON_OUTPUT = "--output=json"
export const DEVPOD_FLAG_OPTION = "--option"
Expand Down
5 changes: 5 additions & 0 deletions desktop/src/client/workspaces/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ export class WorkspacesClient implements TDebuggable {
return this.getStatus(ctx.id)
}

public async troubleshoot(ctx: TWorkspaceClientContext) {
const cmd = WorkspaceCommands.TroubleshootWorkspace(ctx.id)
return cmd.run()
}

public async reset(
listener: TStreamEventListenerFn | undefined,
ctx: TWorkspaceClientContext
Expand Down
5 changes: 5 additions & 0 deletions desktop/src/client/workspaces/workspaceCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
DEVPOD_COMMAND_STATUS,
DEVPOD_COMMAND_STOP,
DEVPOD_COMMAND_UP,
DEVPOD_COMMAND_TROUBLESHOOT,
DEVPOD_FLAG_DEBUG,
DEVPOD_FLAG_DEVCONTAINER_PATH,
DEVPOD_FLAG_FORCE,
Expand Down Expand Up @@ -206,6 +207,10 @@ export class WorkspaceCommands {
])
}

static TroubleshootWorkspace(id: TWorkspaceID) {
return WorkspaceCommands.newCommand([DEVPOD_COMMAND_TROUBLESHOOT, id])
}

static RemoveWorkspace(id: TWorkspaceID, force?: boolean) {
const args = [DEVPOD_COMMAND_DELETE, id, DEVPOD_FLAG_JSON_LOG_OUTPUT]
if (force) {
Expand Down
73 changes: 73 additions & 0 deletions desktop/src/lib/useStoreTroubleshoot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { client } from "@/client"
import { TActionObj } from "@/contexts/DevPodContext/action"
import { TWorkspace } from "@/types"
import { useToast } from "@chakra-ui/react"
import { useMutation } from "@tanstack/react-query"
import { ProWorkspaceInstance } from "@/contexts"

export function useStoreTroubleshoot() {
const toast = useToast()
const { mutate, isLoading: isStoring } = useMutation({
mutationFn: async ({
workspace,
workspaceActions,
}: {
workspace: TWorkspace | ProWorkspaceInstance
workspaceActions: TActionObj[]
}) => {
const logFiles = await Promise.all(
workspaceActions.map((action) => client.workspaces.getActionLogFile(action.id))
)

const targetFolder = await client.selectFromDir("Save Troubleshooting Data")

// user cancelled "save file" dialog
if (targetFolder === null) {
return
}

const unwrappedLogFiles = logFiles
.filter((f) => f.ok)
.map((f) => f.unwrap() ?? "")
.map((f) => [[f], [targetFolder, f.split("/").pop() ?? ""]])
// poor mans zip
await Promise.all(
unwrappedLogFiles.map(([src, target]) => client.copyFilePaths(src ?? [], target ?? []))
)

await client.writeTextFile(
[targetFolder, "workspace_actions.json"],
JSON.stringify(workspaceActions, null, 2)
)

await client.writeTextFile(
[targetFolder, "workspace.json"],
JSON.stringify(workspace, null, 2)
)

const troubleshootOutput = await client.workspaces.troubleshoot({
id: workspace.id,
actionID: "",
streamID: "",
})
if (troubleshootOutput.ok) {
await client.writeTextFile(
[targetFolder, "cli_troubleshoot.json"],
troubleshootOutput.unwrap().stdout
)
}

client.open(targetFolder)
},
onError(error) {
toast({
title: `Failed to save logs: ${error}`,
status: "error",
isClosable: true,
duration: 30_000, // 30 sec
})
},
})

return { store: mutate, isStoring }
}
15 changes: 15 additions & 0 deletions desktop/src/views/Pro/Workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
useProjectClusters,
useTemplates,
useWorkspace,
useWorkspaceActions,
} from "@/contexts"
import { Clock, Folder, Git, Globe, Image, Status } from "@/icons"
import {
Expand All @@ -27,6 +28,7 @@ import { BackToWorkspaces } from "../BackToWorkspaces"
import { WorkspaceTabs } from "./Tabs"
import { WorkspaceCardHeader } from "./WorkspaceCardHeader"
import { WorkspaceStatus } from "./WorkspaceStatus"
import { useStoreTroubleshoot } from "@/lib/useStoreTroubleshoot"

export function Workspace() {
const params = useParams<{ workspace: string }>()
Expand All @@ -37,6 +39,7 @@ export function Workspace() {
const workspace = useWorkspace<ProWorkspaceInstance>(params.workspace)
const instance = workspace.data
const instanceDisplayName = getDisplayName(instance)
const workspaceActions = useWorkspaceActions(instance?.id)

const { modal: stopModal, open: openStopModal } = useStopWorkspaceModal(
useCallback(
Expand Down Expand Up @@ -115,6 +118,8 @@ export function Workspace() {
)
}

const { store: storeTroubleshoot } = useStoreTroubleshoot()

const canStop =
instance.status?.lastWorkspaceStatus != "Busy" &&
instance.status?.lastWorkspaceStatus != "Stopped"
Expand All @@ -130,6 +135,15 @@ export function Workspace() {

const lastActivity = getLastActivity(instance)

const handleTroubleshootClicked = useCallback(() => {
if (workspace.data && workspaceActions) {
storeTroubleshoot({
workspace: workspace.data,
workspaceActions: workspaceActions,
})
}
}, [storeTroubleshoot, workspace.data, workspaceActions])

return (
<>
<VStack align="start" width="full" height="full">
Expand All @@ -143,6 +157,7 @@ export function Workspace() {
onRebuildClicked={openRebuildModal}
onResetClicked={openResetModal}
onStopClicked={!canStop ? openStopModal : workspace.stop}
onTroubleshootClicked={handleTroubleshootClicked}
/>
</WorkspaceCardHeader>
</Box>
Expand Down
Loading

0 comments on commit 77b2ad4

Please sign in to comment.