github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/webui/src/pages/repositories/repository/settings/branches.jsx (about)

     1  import React, {useEffect, useRef, useState} from "react";
     2  import { useOutletContext } from "react-router-dom";
     3  import {AlertError, Loading, RefreshButton} from "../../../../lib/components/controls";
     4  import {useRefs} from "../../../../lib/hooks/repo";
     5  import Card from "react-bootstrap/Card";
     6  import {Button, ListGroup, Row} from "react-bootstrap";
     7  import Col from "react-bootstrap/Col";
     8  import {useAPI} from "../../../../lib/hooks/api";
     9  import {branchProtectionRules} from "../../../../lib/api";
    10  import Modal from "react-bootstrap/Modal";
    11  import Form from "react-bootstrap/Form";
    12  import Alert from "react-bootstrap/Alert";
    13  
    14  const SettingsContainer = () => {
    15      const {repo, loading, error} = useRefs();
    16      const [showCreateModal, setShowCreateModal] = useState(false);
    17      const [refresh, setRefresh] = useState(false);
    18      const [actionError, setActionError] = useState(null);
    19      const [deleteButtonDisabled, setDeleteButtonDisabled] = useState(false)
    20  
    21      const {response: rulesResponse, error: rulesError, loading: rulesLoading} = useAPI(async () => {
    22          return branchProtectionRules.getRules(repo.id)
    23      }, [repo, refresh])
    24      const deleteRule = (pattern) => {
    25          let updatedRules = [...rulesResponse['rules']]
    26          let lastKnownChecksum = rulesResponse['checksum']
    27          updatedRules = updatedRules.filter(r => r.pattern !== pattern)
    28          branchProtectionRules.setRules(repo.id, updatedRules, lastKnownChecksum).then(() => {
    29              setRefresh(!refresh)
    30              setDeleteButtonDisabled(false)
    31          }).catch(err => {
    32              setDeleteButtonDisabled(false)
    33              setActionError(err)
    34          })
    35      }
    36      if (error) return <AlertError error={error}/>;
    37      if (rulesError) return <AlertError error={rulesError}/>;
    38      if (actionError) return <AlertError error={actionError}/>;
    39      return (<>
    40          <div className="mt-3 mb-5">
    41              <div className={"section-title"}>
    42                  <h4 className={"mb-0"}>
    43                      <div className={"ms-1 me-1 pl-0 d-flex"}>
    44                          <div className="flex-grow-1">Branch protection rules</div>
    45                          <RefreshButton className={"ms-1"} onClick={() => {setRefresh(!refresh)}}/>
    46                          <Button className={"ms-2"} onClick={() => setShowCreateModal(true)}>Add</Button>
    47                      </div>
    48                  </h4>
    49              </div>
    50              <div>
    51                  Define branch protection rules to prevent direct changes.&nbsp;
    52                  Changes to protected branches can only be done by merging from other branches.&nbsp;
    53                  {/* eslint-disable-next-line react/jsx-no-target-blank */}
    54                  <a href="https://docs.lakefs.io/reference/protected_branches.html" target="_blank">Learn more.</a>
    55              </div>
    56              {loading || rulesLoading ? <div className={"mt-3 ms-1 pr-5"}><Loading/></div> :
    57                  <div className={"row mt-3 ms-1 pr-5"}>
    58                      <Card className={"w-100 rounded border-0"}>
    59                          <Card.Body className={"p-0 rounded"}>
    60                              <ListGroup>
    61                                  {rulesResponse && rulesResponse['rules'].length > 0 ? rulesResponse['rules'].map((r) => {
    62                                      return <ListGroup.Item key={r.pattern}>
    63                                          <div className="d-flex">
    64                                              <code>{r.pattern}</code>
    65                                              <Button disabled={deleteButtonDisabled} className="ms-auto" size="sm" variant="secondary" onClick={() => deleteRule(r.pattern)}>Delete</Button>
    66                                          </div>
    67                                      </ListGroup.Item>
    68                                  }) : <Alert variant="info">There aren&apos;t any rules yet.</Alert>}
    69                              </ListGroup>
    70                          </Card.Body>
    71                      </Card>
    72                  </div>}
    73          </div>
    74          <CreateRuleModal show={showCreateModal} hideFn={() => setShowCreateModal(false)} currentRulesResponse={rulesResponse} onSuccess={() => {
    75              setRefresh(!refresh)
    76              setShowCreateModal(false)
    77          }} repoID={repo.id}/>
    78      </>);
    79  }
    80  const CreateRuleModal = ({show, hideFn, onSuccess, repoID, currentRulesResponse}) => {
    81      const [error, setError] = useState(null);
    82      const [createButtonDisabled, setCreateButtonDisabled] = useState(true);
    83      const patternField = useRef(null);
    84  
    85      const createRule = (pattern) => {
    86          if (createButtonDisabled) {
    87              return
    88          }
    89          setError(null)
    90          setCreateButtonDisabled(true)
    91          let updatedRules = [...currentRulesResponse['rules']]
    92          let lastKnownChecksum = currentRulesResponse['checksum']
    93          updatedRules.push({pattern})
    94          branchProtectionRules.setRules(repoID, updatedRules, lastKnownChecksum).then(onSuccess).catch(err => {
    95              setError(err)
    96              setCreateButtonDisabled(false)
    97          })
    98      }
    99      return <Modal show={show} onHide={() => {
   100          setCreateButtonDisabled(true)
   101          setError(null)
   102          hideFn()
   103      }}>
   104          <Modal.Header closeButton>
   105              <Modal.Title>Create Branch Protection Rule</Modal.Title>
   106          </Modal.Header>
   107  
   108          <Modal.Body className={"w-100"}>
   109              <Form onSubmit={(e) => {
   110                  e.preventDefault();
   111                  createRule(patternField.current.value);
   112              }}>
   113                  <Form.Group as={Row} controlId="pattern">
   114                      <Form.Label column sm={4}>Branch name pattern</Form.Label>
   115                      <Col>
   116                          <Form.Control sm={8} type="text" autoFocus ref={patternField}
   117                                        onChange={() => setCreateButtonDisabled(!patternField.current || !patternField.current.value)}/>
   118                      </Col>
   119                  </Form.Group>
   120              </Form>
   121              {error && <AlertError error={error}/>}
   122          </Modal.Body>
   123          <Modal.Footer>
   124              <Button disabled={createButtonDisabled} onClick={() => createRule(patternField.current.value)}
   125                      variant="success">Create</Button>
   126              <Button onClick={() => {
   127                  setCreateButtonDisabled(true)
   128                  setError(null)
   129                  hideFn()
   130              }} variant="secondary">Cancel</Button>
   131          </Modal.Footer>
   132      </Modal>
   133  }
   134  
   135  const RepositorySettingsBranchesPage = () => {
   136    const [setActiveTab] = useOutletContext();
   137    useEffect(() => setActiveTab("branches"), [setActiveTab]);
   138    return <SettingsContainer />;
   139  };
   140  
   141  export default RepositorySettingsBranchesPage;