diff --git a/internal/config/config.go b/internal/config/config.go index cb8f25cc..3f86a681 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -54,6 +54,7 @@ type Config struct { APIBaseURL string // Base URL for API Debug bool // Enable debug mode (verbose logging) LogFormat string // Log format + TimeZone string // The server time zone } type TLS struct { @@ -178,6 +179,7 @@ func bindEnvs() { _ = viper.BindEnv("navbarColor", "DAGU_NAVBAR_COLOR") _ = viper.BindEnv("navbarTitle", "DAGU_NAVBAR_TITLE") _ = viper.BindEnv("apiBaseURL", "DAGU_API_BASE_URL") + _ = viper.BindEnv("timeZone", "DAGU_TIME_ZONE") // Basic authentication _ = viper.BindEnv("isBasicAuth", "DAGU_IS_BASICAUTH") diff --git a/internal/frontend/frontend.go b/internal/frontend/frontend.go index 49eea471..072c8802 100644 --- a/internal/frontend/frontend.go +++ b/internal/frontend/frontend.go @@ -43,6 +43,7 @@ func New(cfg *config.Config, lg logger.Logger, cli client.Client) *server.Server NavbarColor: cfg.NavbarColor, NavbarTitle: cfg.NavbarTitle, APIBaseURL: cfg.APIBaseURL, + TimeZone: cfg.TimeZone, } if cfg.IsAuthToken { diff --git a/internal/frontend/server/server.go b/internal/frontend/server/server.go index a702efea..e768f9b4 100644 --- a/internal/frontend/server/server.go +++ b/internal/frontend/server/server.go @@ -63,6 +63,7 @@ type NewServerArgs struct { NavbarColor string NavbarTitle string APIBaseURL string + TimeZone string } type BasicAuth struct { @@ -92,6 +93,7 @@ func New(params NewServerArgs) *Server { NavbarColor: params.NavbarColor, NavbarTitle: params.NavbarTitle, APIBaseURL: params.APIBaseURL, + TimeZone: params.TimeZone, }, } } diff --git a/internal/frontend/server/templates.go b/internal/frontend/server/templates.go index 19147623..7ca88352 100644 --- a/internal/frontend/server/templates.go +++ b/internal/frontend/server/templates.go @@ -57,6 +57,7 @@ type funcsConfig struct { NavbarColor string NavbarTitle string APIBaseURL string + TimeZone string } func defaultFunctions(cfg funcsConfig) template.FuncMap { @@ -80,6 +81,9 @@ func defaultFunctions(cfg funcsConfig) template.FuncMap { "apiURL": func() string { return cfg.APIBaseURL }, + "timeZone": func() string { + return cfg.TimeZone + }, } } diff --git a/internal/frontend/templates/base.gohtml b/internal/frontend/templates/base.gohtml index 4b921bf2..dccc1bb2 100644 --- a/internal/frontend/templates/base.gohtml +++ b/internal/frontend/templates/base.gohtml @@ -1,27 +1,26 @@ {{define "base"}} - - - - - - {{navbarTitle}} - - - - - {{template "content" .}} - - + + + + + {{template "content" .}} + -{{ end }} \ No newline at end of file +{{ end }} diff --git a/ui/index.html b/ui/index.html index 7b3bf47b..73417378 100644 --- a/ui/index.html +++ b/ui/index.html @@ -12,6 +12,7 @@ title: '', navbarColor: '', version: '', + timeZone: '', }; } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index b2614057..80e81f0f 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -13,6 +13,7 @@ export type Config = { apiURL: string; title: string; navbarColor: string; + timeZone: string; version: string; }; diff --git a/ui/src/components/molecules/DAGTable.tsx b/ui/src/components/molecules/DAGTable.tsx index 625f6bc7..39b30216 100644 --- a/ui/src/components/molecules/DAGTable.tsx +++ b/ui/src/components/molecules/DAGTable.tsx @@ -251,7 +251,9 @@ const defaultColumns = [ }), columnHelper.accessor('Type', { id: 'Schedule', - header: 'Schedule', + header: getConfig().timeZone + ? `Schedule in ${getConfig().timeZone}` + : 'Schedule', enableSorting: true, cell: (props) => { const data = props.row.original!; @@ -389,7 +391,15 @@ const defaultColumns = [ }), ]; -function DAGTable({ DAGs = [], group = '', refreshFn, searchText, handleSearchTextChange, searchTag, handleSearchTagChange }: Props) { +function DAGTable({ + DAGs = [], + group = '', + refreshFn, + searchText, + handleSearchTextChange, + searchTag, + handleSearchTagChange, +}: Props) { const [columns] = React.useState(() => [ ...defaultColumns, ]); @@ -444,7 +454,7 @@ function DAGTable({ DAGs = [], group = '', refreshFn, searchText, handleSearchTe const instance = useReactTable({ data, columns, - getSubRows: (row) =>row.subRows, + getSubRows: (row) => row.subRows, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), @@ -497,21 +507,19 @@ function DAGTable({ DAGs = [], group = '', refreshFn, searchText, handleSearchTe limitTags={1} value={searchTag} freeSolo - options={ - DAGs.reduce((acc, dag) => { - if (dag.Type == DAGDataType.DAG) { - const tags = dag.DAGStatus.DAG.Tags; - if (tags) { - tags.forEach((tag) => { - if (!acc.includes(tag)) { - acc.push(tag); - } - }); - } + options={DAGs.reduce((acc, dag) => { + if (dag.Type == DAGDataType.DAG) { + const tags = dag.DAGStatus.DAG.Tags; + if (tags) { + tags.forEach((tag) => { + if (!acc.includes(tag)) { + acc.push(tag); + } + }); } - return acc; - }, []) - } + } + return acc; + }, [])} onChange={(_, value) => { const v = value || ''; handleSearchTagChange(v); @@ -558,9 +566,9 @@ function DAGTable({ DAGs = [], group = '', refreshFn, searchText, handleSearchTe {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext() - )} + header.column.columnDef.header, + header.getContext() + )} {{ asc: ( - cronParser.parseExpression(s.Expression).next() - ); + const tz = getConfig().timeZone; + const datesToRun = schedules.map((s) => { + const expression = tz + ? cronParser.parseExpression(s.Expression, { + currentDate: new Date(), + tz, + }) + : cronParser.parseExpression(s.Expression); + return expression.next(); + }); const sorted = datesToRun.sort((a, b) => a.getTime() - b.getTime()); return sorted[0].getTime() / 1000; }