Skip to content

Commit

Permalink
feat: implement add plan UI
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed Nov 12, 2023
1 parent 7d4efdf commit 9288589
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 82 deletions.
34 changes: 19 additions & 15 deletions internal/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ func ValidateConfig(c *v1.Config) error {
if e := validateRepo(repo); e != nil {
err = multierror.Append(e, fmt.Errorf("repo %s: %w", repo.GetId(), err))
}
if _, ok := repos[repo.GetId()]; ok {
if _, ok := repos[repo.Id]; ok {
err = multierror.Append(err, fmt.Errorf("repo %s: duplicate id", repo.GetId()))
}
repos[repo.GetId()] = repo
repos[repo.Id] = repo
}
}

if c.Plans != nil {
plans := make(map[string]*v1.Plan)
for _, plan := range c.Plans {
if _, ok := plans[plan.Id]; ok {
err = multierror.Append(err, fmt.Errorf("plan %s: duplicate id", plan.GetId()))
}
plans[plan.Id] = plan
if e := validatePlan(plan, repos); e != nil {
err = multierror.Append(err, fmt.Errorf("plan %s: %w", plan.GetId(), e))
}
Expand All @@ -39,19 +44,19 @@ func ValidateConfig(c *v1.Config) error {

func validateRepo(repo *v1.Repo) error {
var err error
if repo.GetId() == "" {
if repo.Id == "" {
err = multierror.Append(err, errors.New("id is required"))
}

if repo.GetUri() == "" {
if repo.Uri == "" {
err = multierror.Append(err, errors.New("uri is required"))
}

if repo.GetPassword() == "" {
if repo.Password == "" {
err = multierror.Append(err, errors.New("password is required"))
}

for _, env := range repo.GetEnv() {
for _, env := range repo.Env {
if !strings.Contains(env, "=") {
err = multierror.Append(err, fmt.Errorf("invalid env var %s, must take format KEY=VALUE", env))
}
Expand All @@ -75,12 +80,12 @@ func validatePlan(plan *v1.Plan, repos map[string]*v1.Repo) error {
}


if _, e := cronexpr.Parse(plan.GetCron()); e != nil {
err = multierror.Append(err, fmt.Errorf("invalid cron %q: %w", plan.GetCron(), e))
if _, e := cronexpr.Parse(plan.Cron); e != nil {
err = multierror.Append(err, fmt.Errorf("invalid cron %q: %w", plan.Cron, e))
}

if plan.GetRetention() != nil {
if e := validateRetention(plan.GetRetention()); e != nil {
if e := validateRetention(plan.Retention); e != nil {
err = multierror.Append(err, fmt.Errorf("invalid retention policy: %w", e))
}
}
Expand All @@ -90,20 +95,19 @@ func validatePlan(plan *v1.Plan, repos map[string]*v1.Repo) error {

func validateRetention(policy *v1.RetentionPolicy) error {
var err error
if policy.GetKeepWithinDuration() != "" {
match, e := regexp.Match(`(\d+h)?(\d+m)?(\d+s)?`, []byte(policy.GetKeepWithinDuration()))
if policy.KeepWithinDuration != "" {
match, e := regexp.Match(`(\d+h)?(\d+m)?(\d+s)?`, []byte(policy.KeepWithinDuration))
if e != nil {
panic(e) // regex error
}
if !match {
err = multierror.Append(err, fmt.Errorf("invalid keep_within_duration %q", policy.GetKeepWithinDuration()))
err = multierror.Append(err, fmt.Errorf("invalid keep_within_duration %q", policy.KeepWithinDuration))
}

if policy.GetKeepLastN() != 0 || policy.GetKeepHourly() != 0 || policy.GetKeepDaily() != 0 || policy.GetKeepWeekly() != 0 || policy.GetKeepMonthly() != 0 || policy.GetKeepYearly() != 0 {
if policy.KeepLastN != 0 || policy.KeepHourly != 0 || policy.KeepDaily != 0 || policy.KeepWeekly != 0 || policy.KeepMonthly != 0 || policy.KeepYearly != 0 {
err = multierror.Append(err, fmt.Errorf("keep_within_duration cannot be used with other retention settings"))
}
} else {
if policy.GetKeepLastN() == 0 && policy.GetKeepHourly() == 0 && policy.GetKeepDaily() == 0 && policy.GetKeepWeekly() == 0 && policy.GetKeepMonthly() == 0 && policy.GetKeepYearly() == 0 {
if policy.KeepLastN == 0 && policy.KeepHourly == 0 && policy.KeepDaily == 0 && policy.KeepWeekly == 0 && policy.KeepMonthly == 0 && policy.KeepYearly == 0 {
err = multierror.Append(err, fmt.Errorf("at least one retention policy must be set"))
}
}
Expand Down
3 changes: 1 addition & 2 deletions proto/v1/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ message Config {
int32 modno = 1 [json_name="modno"];

// override the hostname tagged on backups. If provided it will be used in addition to tags to group backups.
string host_override = 2 [json_name="host_override"];
string host = 2 [json_name="host"];

repeated Repo repos = 3 [json_name="repos"];

repeated Plan plans = 4 [json_name="plans"];
}

Expand Down
11 changes: 11 additions & 0 deletions webui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"process": "^0.11.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-js-cron": "^5.0.1",
"recoil": "^0.7.7",
"typescript": "^5.2.2"
}
Expand Down
4 changes: 1 addition & 3 deletions webui/src/components/URIAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { StringList } from "../../gen/ts/types/value.pb";

let timeout: NodeJS.Timeout | undefined = undefined;

export const URIAutocomplete = (
props: React.PropsWithChildren<{ disabled: boolean }>
) => {
export const URIAutocomplete = (props: React.PropsWithChildren<any>) => {
const [value, setValue] = useState("");
const [options, setOptions] = useState<{ value: string }[]>([]);
const [showOptions, setShowOptions] = useState<{ value: string }[]>([]);
Expand Down
3 changes: 2 additions & 1 deletion webui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import * as React from "react";
import { createRoot } from "react-dom/client";
import { App } from "./views/App";
import { RecoilRoot } from "recoil";
import ErrorBoundary from "antd/es/alert/ErrorBoundary";
import { AlertContextProvider } from "./components/Alerts";
import { ModalContextProvider } from "./components/ModalManager";

import "react-js-cron/dist/styles.css";

const Root = ({ children }: { children: React.ReactNode }) => {
return (
<RecoilRoot>
Expand Down
15 changes: 15 additions & 0 deletions webui/src/lib/formutil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FormInstance } from "antd";
import type { ValidateErrorEntity } from "rc-field-form/lib/interface";

export const validateForm = async <T>(form: FormInstance<T>) => {
try {
return await form.validateFields();
} catch (e: any) {
if (e.errorFields) {
const firstError = (e as ValidateErrorEntity).errorFields?.[0]
?.errors?.[0];
throw new Error(firstError);
}
throw e;
}
};
1 change: 1 addition & 0 deletions webui/src/lib/patterns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const nameRegex = /^[a-zA-Z0-9_]+$/;
Loading

0 comments on commit 9288589

Please sign in to comment.