mirror of
https://github.com/GlueOps/autoglue.git
synced 2026-02-14 05:10:05 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d4fda17ca | ||
|
|
a59550bd96 | ||
|
|
182ad023e7 | ||
|
|
3b32be1a84 | ||
|
|
8b99c812a6 | ||
|
|
bc287a6956 | ||
|
|
0039e81bca |
@@ -13,13 +13,13 @@ RUN npm i -g yarn pnpm
|
||||
WORKDIR /src
|
||||
|
||||
COPY . .
|
||||
RUN make swagger && make build
|
||||
RUN make clean && make swagger && make ui && make build
|
||||
|
||||
|
||||
#################################
|
||||
# Runtime
|
||||
#################################
|
||||
FROM alpine:3.20
|
||||
FROM alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata \
|
||||
&& addgroup -S app && adduser -S app -G app
|
||||
|
||||
3
Makefile
3
Makefile
@@ -3,6 +3,7 @@ GOINSTALL := $(GOCMD) install
|
||||
BIN ?= autoglue
|
||||
MAIN ?= main.go
|
||||
UI_DIR ?= ui
|
||||
UI_DEST_DIR ?= internal/ui
|
||||
|
||||
SWAG := $(shell command -v swag 2>/dev/null)
|
||||
GMU := $(shell command -v go-mod-upgrade 2>/dev/null)
|
||||
@@ -68,7 +69,7 @@ build: prepare ui swagger
|
||||
|
||||
clean:
|
||||
@echo ">> Cleaning artifacts..."
|
||||
@rm -rf $(BIN) docs/swagger.* docs/docs.go $(UI_DIR)/dist
|
||||
@rm -rf $(BIN) docs/swagger.* docs/docs.go $(UI_DEST_DIR)/dist $(UI_DIR)/dist $(UI_DIR)/node_modules
|
||||
|
||||
dev: swagger
|
||||
@echo ">> Starting Vite (frontend) and Go API (backend)..."
|
||||
|
||||
@@ -37,12 +37,12 @@ services:
|
||||
- postgres:postgres
|
||||
env_file: .env
|
||||
environment:
|
||||
DATABASE_URL: postgres://$DB_USER:$DB_PASSWORD@postgres:5432/$DB_NAME
|
||||
PGWEB_DATABASE_URL: postgres://$DB_USER:$DB_PASSWORD@postgres:5432/$DB_NAME
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit@sha256:7b687e9fbc26252866580819733f2dce47edde9b6bf4444da3321fdd06932f02
|
||||
image: axllent/mailpit@sha256:6abc8e633df15eaf785cfcf38bae48e66f64beecdc03121e249d0f9ec15f0707
|
||||
restart: always
|
||||
ports:
|
||||
- "1025:1025"
|
||||
|
||||
7455
docs/docs.go
7455
docs/docs.go
File diff suppressed because it is too large
Load Diff
7433
docs/swagger.json
7433
docs/swagger.json
File diff suppressed because it is too large
Load Diff
4844
docs/swagger.yaml
4844
docs/swagger.yaml
File diff suppressed because it is too large
Load Diff
@@ -20,10 +20,8 @@ func NewRouter() http.Handler {
|
||||
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{
|
||||
"http://localhost:5173",
|
||||
"http://127.0.0.1:5173",
|
||||
"http://localhost:8080",
|
||||
"http://127.0.0.1:8080",
|
||||
"http://*:5173",
|
||||
"http://*:8080",
|
||||
},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Content-Type", "content-type", "Authorization", "authorization", "X-Org-ID", "x-org-id"},
|
||||
|
||||
@@ -26,6 +26,11 @@ type JobListItem struct {
|
||||
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastError *string `json:"last_error,omitempty"`
|
||||
ResultStatus string `json:"result_status"`
|
||||
Processed int `json:"processed"`
|
||||
Ready int `json:"ready"`
|
||||
Failed int `json:"failed"`
|
||||
ElapsedMs int `json:"elapsed_ms"`
|
||||
}
|
||||
|
||||
type EnqueueReq struct {
|
||||
@@ -127,7 +132,14 @@ func GetActive(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var rows []JobListItem
|
||||
err := db.DB.Model(&models.Job{}).
|
||||
Select("id, queue_name, status, retry_count, max_retry, scheduled_at, started_at, updated_at, last_error").
|
||||
Select(`
|
||||
id, queue_name, status, retry_count, max_retry, scheduled_at, started_at, updated_at, last_error,
|
||||
COALESCE(result->>'status','') AS result_status,
|
||||
COALESCE((result->>'processed')::int, 0) AS processed,
|
||||
COALESCE((result->>'ready')::int, 0) AS ready,
|
||||
COALESCE((result->>'failed')::int, 0) AS failed,
|
||||
COALESCE((result->>'elapsed_ms')::int, 0) AS elapsed_ms
|
||||
`).
|
||||
Where("status = ?", "running").
|
||||
Order("started_at DESC NULLS LAST, updated_at DESC").
|
||||
Limit(limit).
|
||||
@@ -160,7 +172,14 @@ func GetFailures(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var rows []JobListItem
|
||||
err := db.DB.Model(&models.Job{}).
|
||||
Select("id, queue_name, status, retry_count, max_retry, scheduled_at, started_at, updated_at, last_error").
|
||||
Select(`
|
||||
id, queue_name, status, retry_count, max_retry, scheduled_at, started_at, updated_at, last_error,
|
||||
COALESCE(result->>'status','') AS result_status,
|
||||
COALESCE((result->>'processed')::int, 0) AS processed,
|
||||
COALESCE((result->>'ready')::int, 0) AS ready,
|
||||
COALESCE((result->>'failed')::int, 0) AS failed,
|
||||
COALESCE((result->>'elapsed_ms')::int, 0) AS elapsed_ms
|
||||
`).
|
||||
Where("status = ?", "failed").
|
||||
Order("updated_at DESC").
|
||||
Limit(limit).
|
||||
|
||||
@@ -21,12 +21,14 @@ func LoadKPI(db *gorm.DB) (KPI, error) {
|
||||
now := time.Now()
|
||||
dayAgo := now.Add(-24 * time.Hour)
|
||||
|
||||
// Running now
|
||||
if err := db.Model(&models.Job{}).
|
||||
Where("status = ?", "running").
|
||||
Count(&k.RunningNow).Error; err != nil {
|
||||
return k, err
|
||||
}
|
||||
|
||||
// Scheduled in the future
|
||||
if err := db.Model(&models.Job{}).
|
||||
Where("status IN ?", []string{"queued", "scheduled", "pending"}).
|
||||
Where("scheduled_at > ?", now).
|
||||
@@ -34,6 +36,7 @@ func LoadKPI(db *gorm.DB) (KPI, error) {
|
||||
return k, err
|
||||
}
|
||||
|
||||
// Due now (queued/scheduled/pending with scheduled_at <= now)
|
||||
if err := db.Model(&models.Job{}).
|
||||
Where("status IN ?", []string{"queued", "scheduled", "pending"}).
|
||||
Where("scheduled_at <= ?", now).
|
||||
@@ -41,22 +44,25 @@ func LoadKPI(db *gorm.DB) (KPI, error) {
|
||||
return k, err
|
||||
}
|
||||
|
||||
// Sum of 'ready' over successful jobs in last 24h
|
||||
if err := db.Model(&models.Job{}).
|
||||
Where("status = ?", "success").
|
||||
Where("updated_at >= ?", dayAgo).
|
||||
Count(&k.Succeeded24h).Error; err != nil {
|
||||
Select("COALESCE(SUM((result->>'ready')::int), 0)").
|
||||
Where("status = 'success' AND updated_at >= ?", dayAgo).
|
||||
Scan(&k.Succeeded24h).Error; err != nil {
|
||||
return k, err
|
||||
}
|
||||
|
||||
// Sum of 'failed' over successful jobs in last 24h (failures within a “success” run)
|
||||
if err := db.Model(&models.Job{}).
|
||||
Where("status = ?", "failed").
|
||||
Where("updated_at >= ?", dayAgo).
|
||||
Count(&k.Failed24h).Error; err != nil {
|
||||
Select("COALESCE(SUM((result->>'failed')::int), 0)").
|
||||
Where("status = 'success' AND updated_at >= ?", dayAgo).
|
||||
Scan(&k.Failed24h).Error; err != nil {
|
||||
return k, err
|
||||
}
|
||||
|
||||
// Retryable failed job rows (same as before)
|
||||
if err := db.Model(&models.Job{}).
|
||||
Where("status = ?", "failed").
|
||||
Where("status = 'failed'").
|
||||
Where("retry_count < max_retry").
|
||||
Count(&k.Retryable).Error; err != nil {
|
||||
return k, err
|
||||
|
||||
@@ -31,11 +31,28 @@ func LoadPerQueue(db *gorm.DB) ([]QueueRollup, error) {
|
||||
var rr, qd, qf, s24, f24 int64
|
||||
var avgDur *float64
|
||||
|
||||
_ = db.Model(&models.Job{}).Where("queue_name = ? AND status = 'running'", q).Count(&rr).Error
|
||||
_ = db.Model(&models.Job{}).Where("queue_name = ? AND status IN ('queued','scheduled','pending') AND scheduled_at <= ?", q, now).Count(&qd).Error
|
||||
_ = db.Model(&models.Job{}).Where("queue_name = ? AND status IN ('queued','scheduled','pending') AND scheduled_at > ?", q, now).Count(&qf).Error
|
||||
_ = db.Model(&models.Job{}).Where("queue_name = ? AND status = 'success' AND updated_at >= ?", q, dayAgo).Count(&s24).Error
|
||||
_ = db.Model(&models.Job{}).Where("queue_name = ? AND status = 'failed' AND updated_at >= ?", q, dayAgo).Count(&f24).Error
|
||||
_ = db.Model(&models.Job{}).
|
||||
Where("queue_name = ? AND status = 'running'", q).
|
||||
Count(&rr).Error
|
||||
|
||||
_ = db.Model(&models.Job{}).
|
||||
Where("queue_name = ? AND status IN ('queued','scheduled','pending') AND scheduled_at <= ?", q, now).
|
||||
Count(&qd).Error
|
||||
|
||||
_ = db.Model(&models.Job{}).
|
||||
Where("queue_name = ? AND status IN ('queued','scheduled','pending') AND scheduled_at > ?", q, now).
|
||||
Count(&qf).Error
|
||||
|
||||
// Sum result.ready / result.failed over successes in last 24h
|
||||
_ = db.Model(&models.Job{}).
|
||||
Select("COALESCE(SUM((result->>'ready')::int), 0)").
|
||||
Where("queue_name = ? AND status = 'success' AND updated_at >= ?", q, dayAgo).
|
||||
Scan(&s24).Error
|
||||
|
||||
_ = db.Model(&models.Job{}).
|
||||
Select("COALESCE(SUM((result->>'failed')::int), 0)").
|
||||
Where("queue_name = ? AND status = 'success' AND updated_at >= ?", q, dayAgo).
|
||||
Scan(&f24).Error
|
||||
|
||||
_ = db.
|
||||
Model(&models.Job{}).
|
||||
|
||||
206
internal/ui/dist/assets/icons-B5E6SSBo.js
vendored
206
internal/ui/dist/assets/icons-B5E6SSBo.js
vendored
File diff suppressed because one or more lines are too long
1
internal/ui/dist/assets/index-CHoyJPs-.css
vendored
1
internal/ui/dist/assets/index-CHoyJPs-.css
vendored
File diff suppressed because one or more lines are too long
1
internal/ui/dist/assets/index-DSxuk_EI.js
vendored
1
internal/ui/dist/assets/index-DSxuk_EI.js
vendored
File diff suppressed because one or more lines are too long
11
internal/ui/dist/assets/radix-DRmH1vcw.js
vendored
11
internal/ui/dist/assets/radix-DRmH1vcw.js
vendored
File diff suppressed because one or more lines are too long
12
internal/ui/dist/assets/router-CANfZtzM.js
vendored
12
internal/ui/dist/assets/router-CANfZtzM.js
vendored
File diff suppressed because one or more lines are too long
106
internal/ui/dist/assets/vendor-DvippHRz.js
vendored
106
internal/ui/dist/assets/vendor-DvippHRz.js
vendored
File diff suppressed because one or more lines are too long
18
internal/ui/dist/index.html
vendored
18
internal/ui/dist/index.html
vendored
@@ -1,18 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AutoGlue</title>
|
||||
<script type="module" crossorigin src="/assets/index-DSxuk_EI.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/router-CANfZtzM.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-DvippHRz.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/radix-DRmH1vcw.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/icons-B5E6SSBo.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CHoyJPs-.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// NOTE: Vite outputs to ui/dist with assets in dist/assets.
|
||||
// If you add more nested folders in the future, include them here too.
|
||||
|
||||
//go:embed dist/* dist/assets/*
|
||||
//go:embed dist
|
||||
var distFS embed.FS
|
||||
|
||||
// spaFileSystem serves embedded dist/ files with SPA fallback to index.html
|
||||
@@ -37,7 +37,8 @@ func (s spaFileSystem) Open(name string) (fs.File, error) {
|
||||
// BUT only if it's not obviously a static asset extension
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
switch ext {
|
||||
case ".js", ".css", ".map", ".json", ".txt", ".ico", ".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif", ".woff", ".woff2":
|
||||
case ".js", ".css", ".map", ".json", ".txt", ".ico", ".png", ".jpg", ".jpeg",
|
||||
".svg", ".webp", ".gif", ".woff", ".woff2", ".ttf", ".otf", ".eot", ".wasm":
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
|
||||
@@ -89,8 +90,14 @@ func SPAHandler() (http.Handler, error) {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
}
|
||||
|
||||
info, _ := f.Stat()
|
||||
modTime := time.Now()
|
||||
if info != nil {
|
||||
modTime = info.ModTime()
|
||||
}
|
||||
|
||||
// Serve content
|
||||
http.ServeContent(w, r, filePath, time.Now(), file{f})
|
||||
http.ServeContent(w, r, filePath, modTime, file{f})
|
||||
}), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AutoGlue</title>
|
||||
</head>
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@types/react": "19.1.13",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@vitejs/plugin-react": "^5.0.3",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
|
||||
@@ -34,21 +34,6 @@ export default defineConfig({
|
||||
chunkSizeWarningLimit: 1000,
|
||||
outDir: "../internal/ui/dist",
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (!id.includes("node_modules")) return
|
||||
|
||||
if (id.includes("react-router")) return "router"
|
||||
if (id.includes("@radix-ui")) return "radix"
|
||||
if (id.includes("lucide-react") || id.includes("react-icons")) return "icons"
|
||||
if (id.includes("recharts") || id.includes("d3")) return "charts"
|
||||
if (id.includes("date-fns") || id.includes("dayjs")) return "dates"
|
||||
|
||||
return "vendor"
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ["react", "react-dom", "react-router-dom"],
|
||||
|
||||
@@ -1503,12 +1503,12 @@
|
||||
dependencies:
|
||||
undici-types "~7.12.0"
|
||||
|
||||
"@types/react-dom@^19.1.9":
|
||||
"@types/react-dom@19.1.9":
|
||||
version "19.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.9.tgz#5ab695fce1e804184767932365ae6569c11b4b4b"
|
||||
integrity sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==
|
||||
|
||||
"@types/react@^19.1.13":
|
||||
"@types/react@19.1.13":
|
||||
version "19.1.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.13.tgz#fc650ffa680d739a25a530f5d7ebe00cdd771883"
|
||||
integrity sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==
|
||||
|
||||
Reference in New Issue
Block a user