Skip to content

Commit fc1db3f

Browse files
authored
feat: add saved views to Overview (#2308)
1 parent 1a80603 commit fc1db3f

37 files changed

Lines changed: 5067 additions & 868 deletions

api/v1/api.gen.go

Lines changed: 1599 additions & 809 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1/api.yaml

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ tags:
6161
description: "Workspace secret registry operations"
6262
- name: "profiles"
6363
description: "Runtime profile operations for managed environment variables and secrets"
64+
- name: "views"
65+
description: "Saved Overview view configurations (custom Kanban boards)"
6466

6567
paths:
6668
/health:
@@ -9472,6 +9474,213 @@ paths:
94729474
schema:
94739475
$ref: "#/components/schemas/Error"
94749476

9477+
/views:
9478+
get:
9479+
summary: "List saved views"
9480+
description: "Lists all saved Overview view configurations, ordered by creation time."
9481+
operationId: "listViews"
9482+
tags:
9483+
- "views"
9484+
parameters:
9485+
- $ref: "#/components/parameters/RemoteNode"
9486+
responses:
9487+
"200":
9488+
description: "List of views"
9489+
content:
9490+
application/json:
9491+
schema:
9492+
$ref: "#/components/schemas/ViewListResponse"
9493+
"401":
9494+
description: "Not authenticated"
9495+
content:
9496+
application/json:
9497+
schema:
9498+
$ref: "#/components/schemas/Error"
9499+
default:
9500+
description: "Generic error response"
9501+
content:
9502+
application/json:
9503+
schema:
9504+
$ref: "#/components/schemas/Error"
9505+
post:
9506+
summary: "Create a view"
9507+
description: "Creates a saved Overview view configuration. Views are global and shared across users."
9508+
operationId: "createView"
9509+
tags:
9510+
- "views"
9511+
parameters:
9512+
- $ref: "#/components/parameters/RemoteNode"
9513+
requestBody:
9514+
required: true
9515+
content:
9516+
application/json:
9517+
schema:
9518+
$ref: "#/components/schemas/ViewSpec"
9519+
responses:
9520+
"201":
9521+
description: "View created"
9522+
content:
9523+
application/json:
9524+
schema:
9525+
$ref: "#/components/schemas/View"
9526+
"400":
9527+
description: "Invalid request"
9528+
content:
9529+
application/json:
9530+
schema:
9531+
$ref: "#/components/schemas/Error"
9532+
"401":
9533+
description: "Not authenticated"
9534+
content:
9535+
application/json:
9536+
schema:
9537+
$ref: "#/components/schemas/Error"
9538+
"403":
9539+
description: "Not authorized"
9540+
content:
9541+
application/json:
9542+
schema:
9543+
$ref: "#/components/schemas/Error"
9544+
default:
9545+
description: "Generic error response"
9546+
content:
9547+
application/json:
9548+
schema:
9549+
$ref: "#/components/schemas/Error"
9550+
9551+
/views/{viewId}:
9552+
get:
9553+
summary: "Get a view"
9554+
operationId: "getView"
9555+
tags:
9556+
- "views"
9557+
parameters:
9558+
- name: viewId
9559+
in: path
9560+
required: true
9561+
schema:
9562+
type: string
9563+
- $ref: "#/components/parameters/RemoteNode"
9564+
responses:
9565+
"200":
9566+
description: "View"
9567+
content:
9568+
application/json:
9569+
schema:
9570+
$ref: "#/components/schemas/View"
9571+
"401":
9572+
description: "Not authenticated"
9573+
content:
9574+
application/json:
9575+
schema:
9576+
$ref: "#/components/schemas/Error"
9577+
"404":
9578+
description: "Not found"
9579+
content:
9580+
application/json:
9581+
schema:
9582+
$ref: "#/components/schemas/Error"
9583+
default:
9584+
description: "Generic error response"
9585+
content:
9586+
application/json:
9587+
schema:
9588+
$ref: "#/components/schemas/Error"
9589+
put:
9590+
summary: "Update a view"
9591+
operationId: "updateView"
9592+
tags:
9593+
- "views"
9594+
parameters:
9595+
- name: viewId
9596+
in: path
9597+
required: true
9598+
schema:
9599+
type: string
9600+
- $ref: "#/components/parameters/RemoteNode"
9601+
requestBody:
9602+
required: true
9603+
content:
9604+
application/json:
9605+
schema:
9606+
$ref: "#/components/schemas/ViewSpec"
9607+
responses:
9608+
"200":
9609+
description: "View updated"
9610+
content:
9611+
application/json:
9612+
schema:
9613+
$ref: "#/components/schemas/View"
9614+
"400":
9615+
description: "Invalid request"
9616+
content:
9617+
application/json:
9618+
schema:
9619+
$ref: "#/components/schemas/Error"
9620+
"401":
9621+
description: "Not authenticated"
9622+
content:
9623+
application/json:
9624+
schema:
9625+
$ref: "#/components/schemas/Error"
9626+
"403":
9627+
description: "Not authorized"
9628+
content:
9629+
application/json:
9630+
schema:
9631+
$ref: "#/components/schemas/Error"
9632+
"404":
9633+
description: "Not found"
9634+
content:
9635+
application/json:
9636+
schema:
9637+
$ref: "#/components/schemas/Error"
9638+
default:
9639+
description: "Generic error response"
9640+
content:
9641+
application/json:
9642+
schema:
9643+
$ref: "#/components/schemas/Error"
9644+
delete:
9645+
summary: "Delete a view"
9646+
operationId: "deleteView"
9647+
tags:
9648+
- "views"
9649+
parameters:
9650+
- name: viewId
9651+
in: path
9652+
required: true
9653+
schema:
9654+
type: string
9655+
- $ref: "#/components/parameters/RemoteNode"
9656+
responses:
9657+
"204":
9658+
description: "View deleted"
9659+
"401":
9660+
description: "Not authenticated"
9661+
content:
9662+
application/json:
9663+
schema:
9664+
$ref: "#/components/schemas/Error"
9665+
"403":
9666+
description: "Not authorized"
9667+
content:
9668+
application/json:
9669+
schema:
9670+
$ref: "#/components/schemas/Error"
9671+
"404":
9672+
description: "Not found"
9673+
content:
9674+
application/json:
9675+
schema:
9676+
$ref: "#/components/schemas/Error"
9677+
default:
9678+
description: "Generic error response"
9679+
content:
9680+
application/json:
9681+
schema:
9682+
$ref: "#/components/schemas/Error"
9683+
94759684
/secrets:
94769685
get:
94779686
summary: "List secrets"
@@ -16346,6 +16555,103 @@ components:
1634616555
total:
1634716556
type: integer
1634816557

16558+
ViewSpec:
16559+
type: object
16560+
required:
16561+
- name
16562+
- intervalDays
16563+
properties:
16564+
name:
16565+
type: string
16566+
minLength: 1
16567+
maxLength: 100
16568+
description: "Display name for the view."
16569+
type:
16570+
type: string
16571+
enum:
16572+
- kanban
16573+
default: kanban
16574+
description: "Render type. Currently only kanban is supported."
16575+
workspace:
16576+
type: string
16577+
maxLength: 64
16578+
pattern: "^$|^[A-Za-z0-9_-]+$"
16579+
not:
16580+
enum:
16581+
- all
16582+
- default
16583+
- global
16584+
description: "Workspace filter. Empty string means all workspaces; otherwise use a workspace name."
16585+
labels:
16586+
type: array
16587+
maxItems: 50
16588+
items:
16589+
type: string
16590+
minLength: 1
16591+
maxLength: 128
16592+
description: "Label filter (AND logic), each item key or key=value."
16593+
dagName:
16594+
type: string
16595+
maxLength: 255
16596+
description: "DAG name substring filter. Empty matches any."
16597+
intervalDays:
16598+
type: integer
16599+
minimum: 1
16600+
maximum: 30
16601+
description: "Required number of days each row (bucket) groups. Rows scroll back in time by this unit."
16602+
pinned:
16603+
type: boolean
16604+
default: false
16605+
description: "Whether the view is pinned to the left sidebar."
16606+
16607+
View:
16608+
type: object
16609+
required:
16610+
- id
16611+
- name
16612+
- type
16613+
- intervalDays
16614+
- createdAt
16615+
- updatedAt
16616+
properties:
16617+
id:
16618+
type: string
16619+
name:
16620+
type: string
16621+
type:
16622+
type: string
16623+
workspace:
16624+
type: string
16625+
labels:
16626+
type: array
16627+
items:
16628+
type: string
16629+
dagName:
16630+
type: string
16631+
intervalDays:
16632+
type: integer
16633+
pinned:
16634+
type: boolean
16635+
createdBy:
16636+
type: string
16637+
description: "Username of the creator, for display only."
16638+
createdAt:
16639+
type: string
16640+
format: date-time
16641+
updatedAt:
16642+
type: string
16643+
format: date-time
16644+
16645+
ViewListResponse:
16646+
type: object
16647+
required:
16648+
- views
16649+
properties:
16650+
views:
16651+
type: array
16652+
items:
16653+
$ref: "#/components/schemas/View"
16654+
1634916655
CreateWorkspaceRequest:
1635016656
type: object
1635116657
required:

internal/cmd/process/frontend_store_factories.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/dagucloud/dagu/internal/persis/store"
2626
authservice "github.com/dagucloud/dagu/internal/service/auth"
2727
"github.com/dagucloud/dagu/internal/service/frontend"
28+
"github.com/dagucloud/dagu/internal/view"
2829
)
2930

3031
// NewFrontendStoreFactories returns the file-backed persistence wiring for the frontend server.
@@ -47,9 +48,14 @@ func NewFrontendStoreFactories() frontend.StoreFactories {
4748
UpgradeCheckStoreFactory: file.NewUpgradeCheckStore,
4849
AuditStoreFactory: newAuditStore,
4950
EventStoreFactory: file.NewEventStore,
51+
ViewStoreFactory: newViewStore,
5052
}
5153
}
5254

55+
func newViewStore(cfg *config.Config) (view.Store, error) {
56+
return store.NewViewStore(file.NewCollection(cfg.Paths.ViewsDir, file.WithIndentedJSON()))
57+
}
58+
5359
func newBaseConfigStore(filePath string) (baseconfig.Store, error) {
5460
return file.NewBaseConfigStore(filePath)
5561
}

internal/cmn/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ type PathsConfig struct {
391391
ContextsDir string
392392
RemoteNodesDir string
393393
WorkspacesDir string
394+
ViewsDir string
394395
ConfigFileUsed string
395396
}
396397

internal/cmn/config/definition.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ type PathsDef struct {
207207
ContextsDir string `mapstructure:"contexts_dir"`
208208
RemoteNodesDir string `mapstructure:"remote_nodes_dir"`
209209
WorkspacesDir string `mapstructure:"workspaces_dir"`
210+
ViewsDir string `mapstructure:"views_dir"`
210211
}
211212

212213
// SecretsDef configures shared defaults for secret providers.

internal/cmn/config/loader.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ func (l *ConfigLoader) loadPathsConfig(cfg *Config, def Definition) error {
399399
{"ContextsDir", &cfg.Paths.ContextsDir, def.Paths.ContextsDir},
400400
{"RemoteNodesDir", &cfg.Paths.RemoteNodesDir, def.Paths.RemoteNodesDir},
401401
{"WorkspacesDir", &cfg.Paths.WorkspacesDir, def.Paths.WorkspacesDir},
402+
{"ViewsDir", &cfg.Paths.ViewsDir, def.Paths.ViewsDir},
402403
}
403404

404405
for _, m := range pathMappings {
@@ -1424,6 +1425,7 @@ func (l *ConfigLoader) finalizePaths(cfg *Config) {
14241425
{&cfg.Paths.ContextsDir, "contexts"},
14251426
{&cfg.Paths.RemoteNodesDir, "remote-nodes"},
14261427
{&cfg.Paths.WorkspacesDir, "workspaces"},
1428+
{&cfg.Paths.ViewsDir, "views"},
14271429
}
14281430

14291431
for _, dp := range derivedPaths {

internal/cmn/config/loader_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ func TestLoad_Env(t *testing.T) {
273273
ContextsDir: filepath.Join(testPaths, "data", "contexts"), // Derived from DataDir
274274
RemoteNodesDir: filepath.Join(testPaths, "data", "remote-nodes"), // Derived from DataDir
275275
WorkspacesDir: filepath.Join(testPaths, "data", "workspaces"), // Derived from DataDir
276+
ViewsDir: filepath.Join(testPaths, "data", "views"), // Derived from DataDir
276277
},
277278
Secrets: SecretsConfig{
278279
Vault: VaultSecretsConfig{
@@ -740,6 +741,7 @@ scheduler:
740741
ContextsDir: resolvedTestPath(t, "/var/dagu/data/contexts"),
741742
RemoteNodesDir: resolvedTestPath(t, "/var/dagu/data/remote-nodes"),
742743
WorkspacesDir: resolvedTestPath(t, "/var/dagu/data/workspaces"),
744+
ViewsDir: resolvedTestPath(t, "/var/dagu/data/views"),
743745
},
744746
UI: UI{
745747
LogEncodingCharset: "iso-8859-1",

0 commit comments

Comments
 (0)