github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/webui/src/pages/repositories/repository/settings/retention.jsx (about) 1 import React, {useEffect, useState} from "react"; 2 import { useOutletContext } from "react-router-dom"; 3 import { 4 ActionGroup, 5 ActionsBar, 6 AlertError, 7 Loading, 8 RefreshButton, 9 ToggleSwitch 10 } from "../../../../lib/components/controls"; 11 import Button from "react-bootstrap/Button"; 12 import {NotFoundError, retention} from "../../../../lib/api"; 13 import {useAPI} from "../../../../lib/hooks/api"; 14 import {useRefs} from "../../../../lib/hooks/repo"; 15 import Card from "react-bootstrap/Card"; 16 import Table from "react-bootstrap/Table"; 17 import {PolicyEditor} from "../../../../lib/components/policy"; 18 import Alert from "react-bootstrap/Alert"; 19 20 const exampleJson = (defaultBranch) => { 21 return { 22 "default_retention_days": 21, 23 "branches": 24 [ 25 {"branch_id": defaultBranch, "retention_days": 28}, 26 ] 27 } 28 } 29 30 const GCPolicy = ({repo}) => { 31 const [refresh, setRefresh] = useState(true); 32 const [jsonView, setJsonView] = useState(false); 33 const [showCreate, setShowCreate] = useState(false); 34 const [isActionsDisabled, setIsActionsDisabled] = useState(false); 35 36 const {response, error, loading} = useAPI(async () => { 37 return await retention.getGCPolicy(repo.id) 38 }, [repo, refresh]) 39 40 const doRefresh = () => setRefresh(!refresh); 41 const onDelete = async () => { 42 setIsActionsDisabled(true); 43 try { 44 await retention.deleteGCPolicy(repo.id); 45 } 46 catch (err) { 47 setIsActionsDisabled(false); 48 throw err; 49 } 50 setRefresh(!refresh); 51 setIsActionsDisabled(false); 52 } 53 54 const onSubmit = async (policy) => { 55 setIsActionsDisabled(true); 56 try { 57 await retention.setGCPolicy(repo.id, policy); 58 } 59 catch (err) { 60 setIsActionsDisabled(false); 61 throw err; 62 } 63 setShowCreate(false); 64 setRefresh(!refresh); 65 setIsActionsDisabled(false); 66 }; 67 68 const jsonToggleBar = <ActionsBar> 69 <ActionGroup orientation="right"> 70 <ToggleSwitch label={"JSON view"} id={"policy-json-switch"} onChange={setJsonView} 71 defaultChecked={jsonView}/> 72 </ActionGroup> 73 </ActionsBar> 74 const isPolicyNotSet = error && error instanceof NotFoundError 75 const policy = response 76 let content; 77 if (loading) { 78 content = <Loading/>; 79 } else if (error) { 80 content = isPolicyNotSet ? <Alert variant="info" className={"mt-3"}>A garbage collection policy is not set yet.</Alert> : <AlertError error={error}/>; 81 } else if (jsonView) { 82 content = <> 83 <pre className={"policy-body"}>{JSON.stringify(policy, null, 4)}</pre> 84 {jsonToggleBar} 85 </> 86 } else { 87 content = <> 88 <Table borderless> 89 <tbody> 90 <tr key={'branch-default'}> 91 <td><code>Default retention days: {policy.default_retention_days}</code></td> 92 </tr> 93 </tbody> 94 </Table> 95 <Card className={"mb-3"}> 96 {policy.branches && <Table> 97 <thead> 98 <tr> 99 <th width={"80%"}>Branch</th> 100 <th>Retention Days</th> 101 </tr> 102 </thead> 103 <tbody> 104 {policy.branches.map((branch, i) => { 105 return ( 106 <tr key={`branch-${i}`}> 107 <td><code>{branch.branch_id}</code></td> 108 <td><code>{branch.retention_days}</code></td> 109 </tr> 110 ); 111 })} 112 </tbody> 113 </Table>} 114 </Card> 115 {jsonToggleBar} 116 </> 117 } 118 let editorProps = { 119 policy: policy 120 }; 121 if (!!error && error instanceof NotFoundError) { 122 editorProps = { 123 noID: true, 124 isCreate: true, 125 policy: exampleJson(repo.default_branch), 126 } 127 } 128 return <div className="mt-3 mb-5"> 129 <div className={"section-title"}> 130 <h4 className={"mb-0"}> 131 <div className={"ms-1 me-1 pl-0 d-flex"}> 132 <div className="flex-grow-1">Garbage collection policy</div> 133 <RefreshButton className={"ms-1"} onClick={doRefresh}/> 134 {!error && !loading && !isPolicyNotSet && 135 <Button className={"ms-2 btn-secondary"} disabled={isActionsDisabled} onClick={onDelete}>Delete 136 Policy</Button>} 137 <Button className={"ms-2"} disabled={isActionsDisabled} onClick={() => setShowCreate(true)}>Edit Policy</Button> 138 </div> 139 </h4> 140 </div> 141 <div> 142 {/* eslint-disable-next-line react/jsx-no-target-blank */} 143 This policy determines for how long objects are kept in the storage after they are deleted in lakeFS. <a 144 href="https://docs.lakefs.io/howto/garbage-collection/" target="_blank">Learn more.</a> 145 </div> 146 <div className={"mt-3"}> 147 {content} 148 </div> 149 <PolicyEditor 150 onSubmit={onSubmit} 151 onHide={() => setShowCreate(false)} 152 show={showCreate} 153 {...editorProps} 154 /> 155 </div> 156 }; 157 158 159 const RetentionContainer = () => { 160 const {repo, loading, error} = useRefs(); 161 if (loading) return <Loading/>; 162 if (error) return <AlertError error={error}/>; 163 164 return ( 165 <GCPolicy repo={repo}/> 166 ); 167 } 168 169 const RepositoryRetentionPage = () => { 170 const [setActiveTab] = useOutletContext(); 171 useEffect(() => setActiveTab("retention"), [setActiveTab]); 172 return <RetentionContainer />; 173 }; 174 175 export default RepositoryRetentionPage;