mirror of
https://github.com/OpenMaxIO/openmaxio-object-browser
synced 2026-07-01 07:41:18 -07:00
Connected object tags API (#421)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -0,0 +1,138 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button, Grid } from "@material-ui/core";
|
||||||
|
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||||
|
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
|
||||||
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
|
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
|
||||||
|
import api from "../../../../../../common/api";
|
||||||
|
|
||||||
|
interface ITagModal {
|
||||||
|
modalOpen: boolean;
|
||||||
|
currentTags: any;
|
||||||
|
bucketName: string;
|
||||||
|
versionId: string;
|
||||||
|
onCloseAndUpdate: (refresh: boolean) => void;
|
||||||
|
selectedObject: string;
|
||||||
|
classes: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = (theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
buttonContainer: {
|
||||||
|
textAlign: "right",
|
||||||
|
},
|
||||||
|
pathLabel: {
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 32,
|
||||||
|
},
|
||||||
|
...modalBasic,
|
||||||
|
});
|
||||||
|
|
||||||
|
const AddTagModal = ({
|
||||||
|
modalOpen,
|
||||||
|
currentTags,
|
||||||
|
selectedObject,
|
||||||
|
onCloseAndUpdate,
|
||||||
|
bucketName,
|
||||||
|
versionId,
|
||||||
|
classes,
|
||||||
|
}: ITagModal) => {
|
||||||
|
const [newKey, setNewKey] = useState<string>("");
|
||||||
|
const [newLabel, setNewLabel] = useState<string>("");
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setNewLabel("");
|
||||||
|
setNewKey("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTagProcess = () => {
|
||||||
|
setIsSending(true);
|
||||||
|
const newTag: any = {};
|
||||||
|
|
||||||
|
newTag[newKey] = newLabel;
|
||||||
|
const newTagList = { ...currentTags, ...newTag };
|
||||||
|
|
||||||
|
api
|
||||||
|
.invoke(
|
||||||
|
"PUT",
|
||||||
|
`/api/v1/buckets/${bucketName}/objects/tags?prefix=${selectedObject}&version_id=${versionId}`,
|
||||||
|
{ tags: newTagList }
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
setIsSending(false);
|
||||||
|
onCloseAndUpdate(true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setError(error);
|
||||||
|
setIsSending(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ModalWrapper
|
||||||
|
modalOpen={modalOpen}
|
||||||
|
title="Add New Tag"
|
||||||
|
onClose={() => {
|
||||||
|
onCloseAndUpdate(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid container>
|
||||||
|
<h3 className={classes.pathLabel}>
|
||||||
|
Selected Object: {selectedObject}
|
||||||
|
</h3>
|
||||||
|
{error !== "" && <span>{error}</span>}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
value={newKey}
|
||||||
|
label={"New Tag Key"}
|
||||||
|
id={"newTagKey"}
|
||||||
|
name={"newTagKey"}
|
||||||
|
placeholder={"Enter New Tag Key"}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNewKey(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<InputBoxWrapper
|
||||||
|
value={newLabel}
|
||||||
|
label={"New Tag Label"}
|
||||||
|
id={"newTagLabel"}
|
||||||
|
name={"newTagLabel"}
|
||||||
|
placeholder={"Enter New Tag Label"}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNewLabel(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} className={classes.buttonContainer}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
color="primary"
|
||||||
|
className={classes.clearButton}
|
||||||
|
onClick={resetForm}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={
|
||||||
|
newLabel.trim() === "" || newKey.trim() === "" || isSending
|
||||||
|
}
|
||||||
|
onClick={addTagProcess}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ModalWrapper>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withStyles(styles)(AddTagModal);
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
|
LinearProgress,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
|
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
|
||||||
|
import api from "../../../../../../common/api";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
|
||||||
|
interface IDeleteTagModal {
|
||||||
|
deleteOpen: boolean;
|
||||||
|
currentTags: any;
|
||||||
|
bucketName: string;
|
||||||
|
versionId: string;
|
||||||
|
selectedTag: [string, string];
|
||||||
|
onCloseAndUpdate: (refresh: boolean) => void;
|
||||||
|
selectedObject: string;
|
||||||
|
classes: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = (theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
buttonContainer: {
|
||||||
|
textAlign: "right",
|
||||||
|
},
|
||||||
|
pathLabel: {
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 32,
|
||||||
|
},
|
||||||
|
...modalBasic,
|
||||||
|
});
|
||||||
|
|
||||||
|
const DeleteTagModal = ({
|
||||||
|
deleteOpen,
|
||||||
|
currentTags,
|
||||||
|
selectedObject,
|
||||||
|
selectedTag,
|
||||||
|
onCloseAndUpdate,
|
||||||
|
bucketName,
|
||||||
|
versionId,
|
||||||
|
classes,
|
||||||
|
}: IDeleteTagModal) => {
|
||||||
|
const [deleteError, setDeleteError] = useState<string>("");
|
||||||
|
const [deleteLoading, setDeleteSending] = useState<boolean>(false);
|
||||||
|
const [tagKey, tagLabel] = selectedTag;
|
||||||
|
|
||||||
|
const removeTagProcess = () => {
|
||||||
|
setDeleteSending(true);
|
||||||
|
const cleanObject = { ...currentTags };
|
||||||
|
delete cleanObject[tagKey];
|
||||||
|
|
||||||
|
api
|
||||||
|
.invoke(
|
||||||
|
"PUT",
|
||||||
|
`/api/v1/buckets/${bucketName}/objects/tags?prefix=${selectedObject}&version_id=${versionId}`,
|
||||||
|
{ tags: cleanObject }
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
setDeleteSending(false);
|
||||||
|
onCloseAndUpdate(true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setDeleteError(error);
|
||||||
|
setDeleteSending(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={deleteOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onCloseAndUpdate(false);
|
||||||
|
}}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">Delete Tag</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{deleteLoading && <LinearProgress />}
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
Are you sure you want to delete the tag{" "}
|
||||||
|
<b className={classes.wrapText}>
|
||||||
|
{tagKey} : {tagLabel}
|
||||||
|
</b>{" "}
|
||||||
|
from {selectedObject}?
|
||||||
|
{deleteError !== "" && (
|
||||||
|
<React.Fragment>
|
||||||
|
<br />
|
||||||
|
<Typography
|
||||||
|
component="p"
|
||||||
|
variant="body1"
|
||||||
|
className={classes.errorBlock}
|
||||||
|
>
|
||||||
|
{deleteError}
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onCloseAndUpdate(false);
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
disabled={deleteLoading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={removeTagProcess} color="secondary" autoFocus>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withStyles(styles)(DeleteTagModal);
|
||||||
@@ -48,6 +48,8 @@ import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
|||||||
import DeleteObject from "../ListObjects/DeleteObject";
|
import DeleteObject from "../ListObjects/DeleteObject";
|
||||||
import { removeRouteLevel } from "../../../../ObjectBrowser/actions";
|
import { removeRouteLevel } from "../../../../ObjectBrowser/actions";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import AddTagModal from "./AddTagModal";
|
||||||
|
import DeleteTagModal from "./DeleteTagModal";
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -163,8 +165,12 @@ const ObjectDetails = ({
|
|||||||
routesList,
|
routesList,
|
||||||
removeRouteLevel,
|
removeRouteLevel,
|
||||||
}: IObjectDetailsProps) => {
|
}: IObjectDetailsProps) => {
|
||||||
|
const [loadObjectData, setLoadObjectData] = useState<boolean>(true);
|
||||||
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
|
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
|
||||||
const [retentionModalOpen, setRetentionModalOpen] = useState<boolean>(false);
|
const [retentionModalOpen, setRetentionModalOpen] = useState<boolean>(false);
|
||||||
|
const [tagModalOpen, setTagModalOpen] = useState<boolean>(false);
|
||||||
|
const [deleteTagModalOpen, setDeleteTagModalOpen] = useState<boolean>(false);
|
||||||
|
const [selectedTag, setSelectedTag] = useState<[string, string]>(["", ""]);
|
||||||
const [actualInfo, setActualInfo] = useState<IFileInfo>(emptyFile);
|
const [actualInfo, setActualInfo] = useState<IFileInfo>(emptyFile);
|
||||||
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
||||||
const [filterVersion, setFilterVersion] = useState<string>("");
|
const [filterVersion, setFilterVersion] = useState<string>("");
|
||||||
@@ -178,22 +184,32 @@ const ObjectDetails = ({
|
|||||||
const pathInBucket = allPathData.slice(3).join("/");
|
const pathInBucket = allPathData.slice(3).join("/");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api
|
if (loadObjectData) {
|
||||||
.invoke(
|
api
|
||||||
"GET",
|
.invoke(
|
||||||
`/api/v1/buckets/${bucketName}/objects?prefix=${pathInBucket}&with_versions=true`
|
"GET",
|
||||||
)
|
`/api/v1/buckets/${bucketName}/objects?prefix=${pathInBucket}&with_versions=true`
|
||||||
.then((res: IFileInfo[]) => {
|
)
|
||||||
const result = get(res, "objects", []);
|
.then((res: IFileInfo[]) => {
|
||||||
setActualInfo(
|
const result = get(res, "objects", []);
|
||||||
result.find((el: IFileInfo) => el.is_latest) || emptyFile
|
setActualInfo(
|
||||||
);
|
result.find((el: IFileInfo) => el.is_latest) || emptyFile
|
||||||
setVersions(result.filter((el: IFileInfo) => !el.is_latest));
|
);
|
||||||
})
|
setVersions(result.filter((el: IFileInfo) => !el.is_latest));
|
||||||
.catch((error) => {
|
setLoadObjectData(false);
|
||||||
setError(error);
|
})
|
||||||
});
|
.catch((error) => {
|
||||||
}, []);
|
setError(error);
|
||||||
|
setLoadObjectData(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loadObjectData]);
|
||||||
|
|
||||||
|
let tagKeys: string[] = [];
|
||||||
|
|
||||||
|
if (actualInfo.tags) {
|
||||||
|
tagKeys = Object.keys(actualInfo.tags);
|
||||||
|
}
|
||||||
|
|
||||||
const openRetentionModal = () => {
|
const openRetentionModal = () => {
|
||||||
setRetentionModalOpen(true);
|
setRetentionModalOpen(true);
|
||||||
@@ -215,8 +231,9 @@ const ObjectDetails = ({
|
|||||||
console.log("close share modal");
|
console.log("close share modal");
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteTag = () => {
|
const deleteTag = (tagKey: string, tagLabel: string) => {
|
||||||
console.log("delete tag");
|
setSelectedTag([tagKey, tagLabel]);
|
||||||
|
setDeleteTagModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadObject = (path: string) => {
|
const downloadObject = (path: string) => {
|
||||||
@@ -247,6 +264,22 @@ const ObjectDetails = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeAddTagModal = (reloadObjectData: boolean) => {
|
||||||
|
setTagModalOpen(false);
|
||||||
|
|
||||||
|
if (reloadObjectData) {
|
||||||
|
setLoadObjectData(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDeleteTagModal = (reloadObjectData: boolean) => {
|
||||||
|
setDeleteTagModalOpen(false);
|
||||||
|
|
||||||
|
if (reloadObjectData) {
|
||||||
|
setLoadObjectData(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<PageHeader label={"Object Browser"} />
|
<PageHeader label={"Object Browser"} />
|
||||||
@@ -271,6 +304,27 @@ const ObjectDetails = ({
|
|||||||
closeDeleteModalAndRefresh={closeDeleteModal}
|
closeDeleteModalAndRefresh={closeDeleteModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{tagModalOpen && (
|
||||||
|
<AddTagModal
|
||||||
|
modalOpen={tagModalOpen}
|
||||||
|
currentTags={actualInfo.tags}
|
||||||
|
selectedObject={pathInBucket}
|
||||||
|
versionId={actualInfo.version_id}
|
||||||
|
bucketName={bucketName}
|
||||||
|
onCloseAndUpdate={closeAddTagModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{deleteTagModalOpen && (
|
||||||
|
<DeleteTagModal
|
||||||
|
deleteOpen={deleteTagModalOpen}
|
||||||
|
currentTags={actualInfo.tags}
|
||||||
|
selectedObject={pathInBucket}
|
||||||
|
versionId={actualInfo.version_id}
|
||||||
|
bucketName={bucketName}
|
||||||
|
onCloseAndUpdate={closeDeleteTagModal}
|
||||||
|
selectedTag={selectedTag}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12} className={classes.container}>
|
<Grid item xs={12} className={classes.container}>
|
||||||
<Grid item xs={12} className={classes.obTitleSection}>
|
<Grid item xs={12} className={classes.obTitleSection}>
|
||||||
@@ -371,24 +425,25 @@ const ObjectDetails = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} className={classes.tagsContainer}>
|
<Grid item xs={12} className={classes.tagsContainer}>
|
||||||
<div className={classes.tagText}>Tags:</div>
|
<div className={classes.tagText}>Tags:</div>
|
||||||
{actualInfo.tags &&
|
{tagKeys.map((tagKey, index) => {
|
||||||
Object.keys(actualInfo.tags).map((itemKey, index) => {
|
const tag = get(actualInfo, `tags.${tagKey}`, "");
|
||||||
const tag = get(actualInfo, `tags.${itemKey}`, "");
|
if (tag !== "") {
|
||||||
if (tag !== "") {
|
return (
|
||||||
return (
|
<Chip
|
||||||
<Chip
|
key={`chip-${index}`}
|
||||||
key={`chip-${index}`}
|
className={classes.tag}
|
||||||
className={classes.tag}
|
size="small"
|
||||||
size="small"
|
label={`${tagKey} : ${tag}`}
|
||||||
label={tag}
|
color="primary"
|
||||||
color="primary"
|
deleteIcon={<CloseIcon />}
|
||||||
deleteIcon={<CloseIcon />}
|
onDelete={() => {
|
||||||
onDelete={deleteTag}
|
deleteTag(tagKey, tag);
|
||||||
/>
|
}}
|
||||||
);
|
/>
|
||||||
}
|
);
|
||||||
return null;
|
}
|
||||||
})}
|
return null;
|
||||||
|
})}
|
||||||
<Chip
|
<Chip
|
||||||
className={classes.tag}
|
className={classes.tag}
|
||||||
icon={<AddIcon />}
|
icon={<AddIcon />}
|
||||||
@@ -397,6 +452,9 @@ const ObjectDetails = ({
|
|||||||
label="Add tag"
|
label="Add tag"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
setTagModalOpen(true);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} className={classes.actionsTray}>
|
<Grid item xs={12} className={classes.actionsTray}>
|
||||||
|
|||||||
Reference in New Issue
Block a user