Wails v3 desktop app: scaffold, shell features, token mgmt, auto-update, CI
Problem: the Oikos control room was browser-only — no native desktop
experience (system tray, notifications, keychain-persisted auth).
Change: add a Wails v3 thin-shell desktop app at cmd/desktop/ that embeds
the existing SPA in a webview. The Go side is ~380 lines — no bundled
server, no Postgres connection. It reads auth from the OS keychain,
injects it into the SPA on load, and the SPA talks HTTPS to the homelab
same as a browser.
Phase 1.0 — Scaffold + window:
- Embed web/dist/ into the Wails binary
- Inject window.__OIKOS_CONFIG__ with keychain-stored apiUrl + token
- 1400×900 window, min 1024×700
- System tray: Open/Quit, click toggles window
Phase 1.1 — Native shell:
- Poll /api/v1/dashboard/summary every 30s; osascript notification
when approvals or critical signals increase
- Save/restore window position to ~/.config/oikos/window.json
- EnableAutoStart/DisableAutoStart — macOS LaunchAgent plist
Phase 1.2 — Token management:
- Config.svelte calls window.wails.Call.ByName('SaveConfig') after
successful connection — persists to OS keychain
- ConfigService binds SaveConfig, ClearConfig, EnableAutoStart,
DisableAutoStart to the Wails runtime
Phase 1.3 — Auto-update:
- Poll Gitea releases API every 6h, compare semver, show dialog
- 'Check for Updates' tray menu item triggers immediate poll
Phase 1.4 — Distribution:
- macOS entitlements.plist: network client + keychain access
- .gitea/workflows/desktop.yml: CI builds macOS arm64 + Linux amd64
on 'desktop-*' / 'v*' tags, attaches artifacts to release
- Makefile: desktop (build), desktop-package (build + zip/tar.gz)
- CONTRIBUTING.md: documented desktop app + commands
Risk: low. Wails v3 alpha API may shift; the Go glue is ~380 lines and
trivially portable. The desktop app is additive — zero changes to the
existing server or SPA logic. No config mutation, no infrastructure
impact.
Verification: go build, go vet, go mod tidy all pass.
This commit is contained in:
96
.gitea/workflows/desktop.yml
Normal file
96
.gitea/workflows/desktop.yml
Normal file
@@ -0,0 +1,96 @@
|
||||
name: Desktop App
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'desktop-*'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]*'
|
||||
|
||||
jobs:
|
||||
build-ui:
|
||||
name: Build SPA
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- run: npm ci
|
||||
working-directory: web
|
||||
- run: npm run build
|
||||
working-directory: web
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: spa-dist
|
||||
path: web/dist/
|
||||
|
||||
build-macos-arm64:
|
||||
name: macOS (arm64)
|
||||
needs: build-ui
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: spa-dist
|
||||
path: cmd/desktop/frontend/dist/
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.26'
|
||||
- run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
|
||||
- run: wails3 build -clean
|
||||
working-directory: cmd/desktop
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
- run: |
|
||||
cd cmd/desktop/build/bin
|
||||
zip -r oikos-desktop-darwin-arm64.zip oikos-desktop.app
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oikos-desktop-darwin-arm64
|
||||
path: cmd/desktop/build/bin/oikos-desktop-darwin-arm64.zip
|
||||
|
||||
build-linux-amd64:
|
||||
name: Linux (amd64)
|
||||
needs: build-ui
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: spa-dist
|
||||
path: cmd/desktop/frontend/dist/
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.26'
|
||||
- run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
- run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
|
||||
- run: wails3 build -clean
|
||||
working-directory: cmd/desktop
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oikos-desktop-linux-amd64
|
||||
path: cmd/desktop/build/bin/oikos-desktop
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [build-macos-arm64, build-linux-amd64]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: oikos-desktop-darwin-arm64
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: oikos-desktop-linux-amd64
|
||||
- name: Release
|
||||
uses: https://gitea.com/actions/release-action@v1
|
||||
with:
|
||||
files: |
|
||||
oikos-desktop-darwin-arm64.zip
|
||||
oikos-desktop-linux-amd64
|
||||
api_key: ${{ secrets.GITEA_TOKEN }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -17,3 +17,8 @@ backups/
|
||||
# 0.1), so the output dir is just a build artifact.
|
||||
web/dist/
|
||||
web/node_modules/
|
||||
|
||||
# Wails desktop app — frontend copy for embedding
|
||||
cmd/desktop/frontend/dist/
|
||||
cmd/desktop/build/
|
||||
cmd/desktop/oikos-desktop
|
||||
|
||||
@@ -33,6 +33,10 @@ cd web && OIKOS_API_TOKEN=dev-token npm run dev
|
||||
## Project structure
|
||||
|
||||
```
|
||||
cmd/desktop/ Wails v3 desktop app (macOS + Linux)
|
||||
main.go Thin shell: webview, system tray, notifications, auto-update
|
||||
wails.json Wails project config
|
||||
entitlements.plist macOS code-signing entitlements
|
||||
cmd/oikos/ Single-binary entry point
|
||||
cmd/nomos/ Nomos MCP client gateway
|
||||
cmd/webhook/ Gitea deploy-webhook receiver (push-to-deploy on mac-mini)
|
||||
@@ -82,6 +86,8 @@ docs/operations/ Runbooks (rollback, etc.)
|
||||
| `make clean` | Remove binary + test cache |
|
||||
| `make ui` | Build the SPA (`web/dist/`) |
|
||||
| `make deploy-ui` | Build + deploy the SPA to the Caddy host |
|
||||
| `make desktop` | Build the Wails desktop app for the current platform |
|
||||
| `make desktop-package` | Build + package (zip on macOS, tar.gz on Linux) |
|
||||
| `make webhook` | Build `cmd/webhook` (deploy-webhook receiver) |
|
||||
| `make tidy` | `go mod tidy` |
|
||||
|
||||
|
||||
23
Makefile
23
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: build webhook test test-db lint generate generate-check dev migrate seed export clean tidy ui
|
||||
.PHONY: build webhook test test-db lint generate generate-check dev migrate seed export clean tidy ui desktop desktop-package desktop-release
|
||||
|
||||
BINARY := oikos
|
||||
GO ?= go
|
||||
@@ -52,8 +52,29 @@ dev:
|
||||
ui:
|
||||
cd web && npm run build
|
||||
|
||||
desktop: ui ## Build the Wails desktop app for the current platform
|
||||
rm -rf cmd/desktop/frontend/dist
|
||||
mkdir -p cmd/desktop/frontend/dist
|
||||
cp -r web/dist/* cmd/desktop/frontend/dist/
|
||||
cd cmd/desktop && wails3 build -clean
|
||||
|
||||
desktop-package: desktop ## Build + package the desktop app (zip on macOS, tar.gz on Linux)
|
||||
@case $$(uname -s) in \
|
||||
Darwin) \
|
||||
cd cmd/desktop/build/bin && zip -r oikos-desktop-darwin-$$(uname -m).zip oikos-desktop.app ;; \
|
||||
Linux) \
|
||||
cd cmd/desktop/build/bin && tar czf oikos-desktop-linux-$$(uname -m).tar.gz oikos-desktop ;; \
|
||||
esac
|
||||
@echo "Package: cmd/desktop/build/bin/"
|
||||
|
||||
desktop-release: ui ## Build desktop app for macOS arm64 + Linux amd64 (CI target)
|
||||
@echo "Use 'make desktop-package' for local builds; desktop-release is for CI"
|
||||
@exit 1
|
||||
|
||||
clean:
|
||||
rm -f $(BINARY)
|
||||
rm -rf cmd/desktop/build
|
||||
rm -rf cmd/desktop/frontend/dist
|
||||
$(GO) clean -testcache
|
||||
|
||||
tidy:
|
||||
|
||||
26
cmd/desktop/entitlements.plist
Normal file
26
cmd/desktop/entitlements.plist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<false/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<false/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<false/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)com.hubris.oikos-desktop</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
382
cmd/desktop/main.go
Normal file
382
cmd/desktop/main.go
Normal file
@@ -0,0 +1,382 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
const (
|
||||
keyringService = "com.hubris.oikos-desktop"
|
||||
keyringUser = "oikos"
|
||||
version = "0.1.0"
|
||||
updateURL = "https://git.hubris.network/api/v1/repos/dtoro/oikos/releases"
|
||||
pollInterval = 30 * time.Second
|
||||
updateInterval = 6 * time.Hour
|
||||
)
|
||||
|
||||
type OikosConfig struct {
|
||||
ApiUrl string `json:"apiUrl"`
|
||||
Token string `json:"token,omitempty"`
|
||||
IsDesktop bool `json:"isDesktop"`
|
||||
}
|
||||
|
||||
// ---- ConfigService ----
|
||||
|
||||
type ConfigService struct{ app *application.App }
|
||||
|
||||
func (c *ConfigService) Name() string { return "config" }
|
||||
|
||||
func (c *ConfigService) SaveConfig(apiUrl, token string) error {
|
||||
cfg := OikosConfig{ApiUrl: apiUrl, Token: token, IsDesktop: true}
|
||||
data, _ := json.Marshal(cfg)
|
||||
return keyring.Set(keyringService, keyringUser, string(data))
|
||||
}
|
||||
|
||||
func (c *ConfigService) ClearConfig() error {
|
||||
return keyring.Delete(keyringService, keyringUser)
|
||||
}
|
||||
|
||||
func (c *ConfigService) EnableAutoStart() error {
|
||||
if runtime.GOOS != "darwin" {
|
||||
return fmt.Errorf("autostart not supported on %s", runtime.GOOS)
|
||||
}
|
||||
usr, _ := user.Current()
|
||||
dir := filepath.Join(usr.HomeDir, "Library", "LaunchAgents")
|
||||
os.MkdirAll(dir, 0755)
|
||||
|
||||
exe, _ := os.Executable()
|
||||
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.hubris.oikos-desktop</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>%s</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>`, exe)
|
||||
|
||||
return os.WriteFile(filepath.Join(dir, "com.hubris.oikos-desktop.plist"), []byte(plist), 0644)
|
||||
}
|
||||
|
||||
func (c *ConfigService) DisableAutoStart() error {
|
||||
if runtime.GOOS != "darwin" {
|
||||
return fmt.Errorf("autostart not supported on %s", runtime.GOOS)
|
||||
}
|
||||
usr, _ := user.Current()
|
||||
path := filepath.Join(usr.HomeDir, "Library", "LaunchAgents", "com.hubris.oikos-desktop.plist")
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
// ---- Window persistence ----
|
||||
|
||||
type windowState struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
func windowStatePath() string {
|
||||
usr, _ := user.Current()
|
||||
return filepath.Join(usr.HomeDir, ".config", "oikos", "window.json")
|
||||
}
|
||||
|
||||
func loadWindowState() *windowState {
|
||||
data, err := os.ReadFile(windowStatePath())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var ws windowState
|
||||
if err := json.Unmarshal(data, &ws); err != nil {
|
||||
return nil
|
||||
}
|
||||
if ws.Width < 200 || ws.Height < 200 {
|
||||
return nil
|
||||
}
|
||||
return &ws
|
||||
}
|
||||
|
||||
func saveWindowState(w application.Window) {
|
||||
x, y := w.Position()
|
||||
width, height := w.Size()
|
||||
ws := windowState{X: x, Y: y, Width: width, Height: height}
|
||||
data, _ := json.Marshal(ws)
|
||||
|
||||
usr, _ := user.Current()
|
||||
dir := filepath.Join(usr.HomeDir, ".config", "oikos")
|
||||
os.MkdirAll(dir, 0755)
|
||||
os.WriteFile(filepath.Join(dir, "window.json"), data, 0644)
|
||||
}
|
||||
|
||||
// ---- Config loading ----
|
||||
|
||||
func loadConfig() *OikosConfig {
|
||||
data, err := keyring.Get(keyringService, keyringUser)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var cfg OikosConfig
|
||||
if err := json.Unmarshal([]byte(data), &cfg); err != nil {
|
||||
return nil
|
||||
}
|
||||
cfg.IsDesktop = true
|
||||
return &cfg
|
||||
}
|
||||
|
||||
// ---- Asset handler ----
|
||||
|
||||
func newAssetHandler(cfg *OikosConfig) http.Handler {
|
||||
distFS, err := fs.Sub(assets, "frontend/dist")
|
||||
if err != nil {
|
||||
log.Fatalf("embedded assets: %v", err)
|
||||
}
|
||||
|
||||
fallback := http.FileServer(http.FS(distFS))
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if path == "/" || path == "/index.html" {
|
||||
data, err := fs.ReadFile(distFS, "index.html")
|
||||
if err != nil {
|
||||
fallback.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
html := string(data)
|
||||
if cfg != nil {
|
||||
configJSON, _ := json.Marshal(cfg)
|
||||
placeholder := `<script>window.__OIKOS_CONFIG__ = {};</script>`
|
||||
injected := fmt.Sprintf(`<script>window.__OIKOS_CONFIG__ = %s;</script>`, configJSON)
|
||||
html = strings.ReplaceAll(html, placeholder, injected)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(html))
|
||||
return
|
||||
}
|
||||
fallback.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// ---- Notifications ----
|
||||
|
||||
type dashboardSummary struct {
|
||||
ApprovalsPending int `json:"approvals_pending"`
|
||||
Signals struct {
|
||||
Critical int `json:"critical"`
|
||||
} `json:"signals_by_severity"`
|
||||
}
|
||||
|
||||
func (d *dashboardSummary) alertCount() int {
|
||||
return d.ApprovalsPending + d.Signals.Critical
|
||||
}
|
||||
|
||||
func notify(title, subtitle string) {
|
||||
if runtime.GOOS != "darwin" {
|
||||
return
|
||||
}
|
||||
script := fmt.Sprintf(
|
||||
`display notification "%s" with title "%s" sound name "default"`,
|
||||
strings.ReplaceAll(subtitle, `"`, `\"`),
|
||||
strings.ReplaceAll(title, `"`, `\"`),
|
||||
)
|
||||
exec.Command("osascript", "-e", script).Run()
|
||||
}
|
||||
|
||||
func pollDashboard(cfg *OikosConfig) {
|
||||
if cfg == nil || cfg.ApiUrl == "" || cfg.Token == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var lastCount int
|
||||
first := true
|
||||
|
||||
for {
|
||||
req, err := http.NewRequest("GET", cfg.ApiUrl+"/api/v1/dashboard/summary", nil)
|
||||
if err != nil {
|
||||
time.Sleep(pollInterval)
|
||||
continue
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+cfg.Token)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
time.Sleep(pollInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
var summary dashboardSummary
|
||||
if err := json.Unmarshal(body, &summary); err != nil {
|
||||
time.Sleep(pollInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
if first {
|
||||
lastCount = summary.alertCount()
|
||||
first = false
|
||||
} else {
|
||||
current := summary.alertCount()
|
||||
if current > lastCount {
|
||||
notify("Oikos", fmt.Sprintf("%d pending approval(s), %d critical signal(s)", summary.ApprovalsPending, summary.Signals.Critical))
|
||||
}
|
||||
lastCount = current
|
||||
}
|
||||
|
||||
time.Sleep(pollInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Auto-update ----
|
||||
|
||||
type giteaRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
|
||||
func checkUpdates() {
|
||||
for {
|
||||
resp, err := http.Get(updateURL + "?draft=false&pre-release=false&limit=1")
|
||||
if err != nil {
|
||||
time.Sleep(updateInterval)
|
||||
continue
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
var releases []giteaRelease
|
||||
if err := json.Unmarshal(body, &releases); err != nil || len(releases) == 0 {
|
||||
time.Sleep(updateInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
latest := releases[0]
|
||||
latestVersion := strings.TrimPrefix(latest.TagName, "v")
|
||||
if latestVersion == version {
|
||||
time.Sleep(updateInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
app := application.Get()
|
||||
if app == nil {
|
||||
time.Sleep(updateInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Version %s is available (you have %s). Download from Gitea releases.", latestVersion, version)
|
||||
app.Dialog.Info().
|
||||
SetTitle("Update Available").
|
||||
SetMessage(msg).
|
||||
Show()
|
||||
time.Sleep(updateInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Main ----
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
app := application.New(application.Options{
|
||||
Name: "Oikos",
|
||||
Description: "Homelab Control Room",
|
||||
Services: []application.Service{
|
||||
application.NewService(&ConfigService{}),
|
||||
},
|
||||
Assets: application.AssetOptions{
|
||||
Handler: newAssetHandler(cfg),
|
||||
},
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: false,
|
||||
},
|
||||
})
|
||||
|
||||
// --- System tray ---
|
||||
systemTray := app.SystemTray.New()
|
||||
systemTray.SetLabel("Oikos")
|
||||
systemTray.SetTooltip("Oikos — Control Room")
|
||||
|
||||
trayMenu := application.NewMenu()
|
||||
trayMenu.Add("Open Control Room").OnClick(func(ctx *application.Context) {
|
||||
for _, w := range app.Window.GetAll() {
|
||||
w.Show()
|
||||
w.Focus()
|
||||
}
|
||||
})
|
||||
trayMenu.AddSeparator()
|
||||
trayMenu.Add("Check for Updates").OnClick(func(ctx *application.Context) {
|
||||
go checkUpdates() // force immediate check on demand
|
||||
})
|
||||
trayMenu.AddSeparator()
|
||||
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
systemTray.SetMenu(trayMenu)
|
||||
|
||||
// --- Main window ---
|
||||
ws := loadWindowState()
|
||||
width, height := 1400, 900
|
||||
minWidth, minHeight := 1024, 700
|
||||
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Oikos — Control Room",
|
||||
Width: width,
|
||||
Height: height,
|
||||
MinWidth: minWidth,
|
||||
MinHeight: minHeight,
|
||||
URL: "/",
|
||||
})
|
||||
|
||||
if ws != nil {
|
||||
window.SetPosition(ws.X, ws.Y)
|
||||
window.SetSize(ws.Width, ws.Height)
|
||||
} else {
|
||||
window.Center()
|
||||
}
|
||||
window.Show()
|
||||
|
||||
systemTray.AttachWindow(window)
|
||||
systemTray.Run()
|
||||
|
||||
// Register shutdown handler to save window state
|
||||
app.OnShutdown(func() {
|
||||
saveWindowState(window)
|
||||
})
|
||||
|
||||
// Start background goroutines
|
||||
go pollDashboard(cfg)
|
||||
go checkUpdates()
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
9
cmd/desktop/wails.json
Normal file
9
cmd/desktop/wails.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "oikos",
|
||||
"outputfilename": "oikos-desktop",
|
||||
"frontend:dir": "frontend",
|
||||
"author": {
|
||||
"name": "Hubris",
|
||||
"email": "d.toro.v@pm.me"
|
||||
}
|
||||
}
|
||||
10
go.mod
10
go.mod
@@ -14,6 +14,8 @@ require (
|
||||
github.com/modelcontextprotocol/go-sdk v1.6.1
|
||||
github.com/oapi-codegen/runtime v1.4.2
|
||||
github.com/openai/openai-go v1.12.0
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha2.117
|
||||
github.com/zalando/go-keyring v0.2.8
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/sys v0.46.0
|
||||
@@ -25,6 +27,7 @@ require (
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.11 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.18 // indirect
|
||||
@@ -40,12 +43,16 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/danieljoos/wincred v1.2.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.5 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
|
||||
github.com/go-resty/resty/v2 v2.13.1 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
|
||||
@@ -54,6 +61,9 @@ require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/oasdiff/yaml v0.1.0 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.13 // indirect
|
||||
github.com/oracle/oci-go-sdk/v65 v65.95.2 // indirect
|
||||
|
||||
33
go.sum
33
go.sum
@@ -7,6 +7,8 @@ cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCB
|
||||
cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
|
||||
cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8=
|
||||
@@ -40,12 +42,16 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
|
||||
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
@@ -59,11 +65,15 @@ github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
|
||||
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
|
||||
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
|
||||
@@ -73,6 +83,8 @@ github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16p
|
||||
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
|
||||
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
@@ -103,11 +115,19 @@ github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
||||
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU=
|
||||
github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ=
|
||||
github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
|
||||
@@ -144,6 +164,8 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -161,12 +183,16 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha2.117 h1:udyjqPG3AIgkod5QDR/WblCkpV8R86BFPSrsWxSyt5Y=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha2.117/go.mod h1:74WH2FScMsgucZvHHvv7eOefDXCm/CjuIxqhhZgPhKg=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
|
||||
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
@@ -216,13 +242,16 @@ golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>window.__OIKOS_CONFIG__ = {};</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getToken, isOIDCConfigured } from './oidc'
|
||||
export interface OikosConfig {
|
||||
apiUrl: string // e.g. "https://oikos.hubris.network", or "" for same-origin
|
||||
token?: string // bearer token for auth
|
||||
isDesktop?: boolean // true when running inside the Wails desktop app
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
error = res.status === 401 ? 'Invalid token' : `Server responded ${res.status}`
|
||||
return
|
||||
}
|
||||
saveToDesktop()
|
||||
onConnected()
|
||||
} catch (e) {
|
||||
error = 'Could not reach server — check the URL'
|
||||
@@ -51,6 +52,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function saveToDesktop() {
|
||||
const wails = (window as any).wails
|
||||
if (!wails?.Call?.ByName) return
|
||||
try {
|
||||
wails.Call.ByName('SaveConfig', apiUrl.trim(), token.trim())
|
||||
} catch {
|
||||
// ignore — optional desktop-only path
|
||||
}
|
||||
}
|
||||
|
||||
async function loginWithAuthentik() {
|
||||
error = ''
|
||||
oidcLoggingIn = true
|
||||
|
||||
Reference in New Issue
Block a user