Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { Button } from "@humansignal/ui";
import { InlineError } from "../../../components/Error/InlineError";
import { Form, Input } from "../../../components/Form";
import { Oneof } from "../../../components/Oneof/Oneof";
import { ApiContext } from "../../../providers/ApiProvider";
import { cn } from "../../../utils/bem";
import { isDefined } from "../../../utils/helpers";
 
export const StorageForm = forwardRef(({ onSubmit, target, project, rootClass, storage, storageTypes }, ref) => {
  /**@type {import('react').RefObject<Form>} */
  const api = useContext(ApiContext);
  const formRef = ref ?? useRef();
  const [type, setType] = useState(storage?.type ?? storageTypes?.[0]?.name ?? "s3");
  const [checking, setChecking] = useState(false);
  const [connectionValid, setConnectionValid] = useState(null);
  const [formFields, setFormFields] = useState([]);
 
  useEffect(() => {
    api
      .callApi("storageForms", {
        params: {
          target,
          type,
        },
      })
      .then((formFields) => setFormFields(formFields ?? []));
  }, [type]);
 
  const storageTypeSelect = {
    columnCount: 1,
    fields: [
      {
        skip: true,
        type: "select",
        name: "storage_type",
        label: "存储类型",
        disabled: !!storage,
        options: storageTypes.map(({ name, title }) => ({
          value: name,
          label: title,
        })),
        value: storage?.type ?? type,
        onChange: setType,
      },
    ],
  };
 
  const validateStorageConnection = useCallback(async () => {
    setChecking(true);
    setConnectionValid(null);
 
    const form = formRef.current;
 
    if (form && form.validateFields()) {
      const body = form.assembleFormData({ asJSON: true });
      const type = form.getField("storage_type").value;
 
      if (isDefined(storage?.id)) {
        body.id = storage.id;
      }
 
      // we're using api provided by the form to be able to save
      // current api context and render inline erorrs properly
      const response = await form.api.callApi("validateStorage", {
        params: {
          target,
          type,
        },
        body,
      });
 
      if (response?.$meta?.ok) setConnectionValid(true);
      else setConnectionValid(false);
    }
    setChecking(false);
  }, [formRef, target, type, storage]);
 
  const action = useMemo(() => {
    return storage ? "updateStorage" : "createStorage";
  }, [storage]);
 
  return (
    <Form.Builder
      ref={formRef}
      action={action}
      params={{ target, type, project, pk: storage?.id }}
      fields={[storageTypeSelect, ...(formFields ?? [])]}
      formData={{ ...(storage ?? {}) }}
      skipEmpty={false}
      onSubmit={onSubmit}
      autoFill="off"
      autoComplete="off"
    >
      <Input type="hidden" name="project" value={project} />
      <Form.Actions
        valid={connectionValid}
        extra={
          connectionValid !== null && (
            <div className={cn("form-indicator").toClassName()}>
              <Oneof value={connectionValid}>
                <span className={cn("form-indicator").elem("item").mod({ type: "success" }).toClassName()} case={true}>
                  连接成功!
                </span>
                <span className={cn("form-indicator").elem("item").mod({ type: "fail" }).toClassName()} case={false}>
                  连接失败
                </span>
              </Oneof>
            </div>
          )
        }
      >
        <Input type="hidden" name="project" value={project} />
        <div className="flex gap-tight">
          <Button
            type="button"
            look="outlined"
            waiting={checking}
            onClick={validateStorageConnection}
            aria-label="测试存储连接"
          >
            检查连接
          </Button>
          <Button type="submit" aria-label={storage ? "保存存储设置" : "添加存储"}>
            {storage ? "保存" : "添加存储"}
          </Button>
        </div>
      </Form.Actions>
 
      <InlineError />
    </Form.Builder>
  );
});