Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow a person to tag buckets. #419

Merged
merged 1 commit into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/_static/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,37 @@ paths:
description: Unauthorized
404:
description: Not Found
post:
tags:
- Frontend
summary: Update user created container metadata.
parameters:
- name: container
in: query
description: The container which metadata will be updated.
schema:
type: string
example: test-container-1
required: true
requestBody:
description: Bucket metadata as a key-value object. Updates must include all the metadata for the bucket. Omitted keys are removed by the swift backend.
required: true
content:
application/json:
schema:
type: object
properties:
key:
type: string
example:
owner: project-team
responses:
204:
description: Container metadata was updated. No Content.
403:
description: Unauthorized
404:
description: Container was not found
/api/shared/objects:
get:
tags:
Expand Down
36 changes: 34 additions & 2 deletions swift_browser_ui/ui/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,20 @@ async def swift_create_container(request: aiohttp.web.Request) -> aiohttp.web.Re
request.app["Log"].info(
f"API call for container creation from {request.remote}, sess {session}"
)

req_json = await request.json()
tags = req_json.get("tags", None)

headers = {}
if tags:
headers["X-Container-Meta-UserTags"] = tags

# Shamelessly use private methods from SwiftService to avoid writing
# own implementation
res = request.app["Sessions"][session]["ST_conn"]._create_container_job(
get_conn(request.app["Sessions"][session]["ST_conn"]._options),
request.match_info["container"],
conn=get_conn(request.app["Sessions"][session]["ST_conn"]._options),
container=request.match_info["container"],
headers=headers,
)
except (SwiftError, ClientException):
request.app["Log"].error("Container creation failed.")
Expand Down Expand Up @@ -527,6 +536,29 @@ async def get_metadata_bucket(request: aiohttp.web.Request) -> aiohttp.web.Respo
return aiohttp.web.json_response([ret["container"], ret["headers"]])


async def update_metadata_bucket(request: aiohttp.web.Request) -> aiohttp.web.Response:
"""Update metadata for a container."""
session = api_check(request)
request.app["Log"].info(
"API cal for updating container metadata from "
f"{request.remote}, sess: {session} :: {time.ctime()}"
)

# Get required variables from query string
container = request.query.get("container", "") or None
meta = await request.json()

meta = [(key, value) for key, value in meta.items()]

conn = request.app["Sessions"][session]["ST_conn"]
ret = conn.post(container=container, options={"meta": meta})

if not ret["success"]:
raise aiohttp.web.HTTPNotFound

return aiohttp.web.HTTPNoContent()


async def get_metadata_object(request: aiohttp.web.Request) -> aiohttp.web.Response:
"""Get metadata for a container or for an object."""
session = api_check(request)
Expand Down
2 changes: 2 additions & 0 deletions swift_browser_ui/ui/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
swift_upload_object_chunk,
swift_check_object_chunk,
swift_replicate_container,
update_metadata_bucket,
)
from swift_browser_ui.ui.health import handle_health_check
from swift_browser_ui.ui.settings import setd
Expand Down Expand Up @@ -158,6 +159,7 @@ async def servinit() -> aiohttp.web.Application:
aiohttp.web.get("/api/projects", os_list_projects),
aiohttp.web.get("/api/project/active", get_os_active_project),
aiohttp.web.get("/api/bucket/meta", get_metadata_bucket),
aiohttp.web.post("/api/bucket/meta", update_metadata_bucket),
aiohttp.web.get("/api/bucket/object/meta", get_metadata_object),
aiohttp.web.get("/api/project/meta", get_project_metadata),
aiohttp.web.get("/api/project/acl", get_access_control_metadata),
Expand Down
46 changes: 44 additions & 2 deletions swift_browser_ui_frontend/src/common/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,40 @@ export async function getBuckets () {
return buckets;
}

export async function getBucketMeta (
container,
){
let url = new URL(
"/api/bucket/meta?container=".concat(encodeURI(container)),
document.location.origin,
);

let ret = await fetch(
url, {method: "GET", credentials: "same-origin"},
);
return ret.json();
}

export async function updateBucketMeta (
container,
metadata,
){
let url = new URL(
"/api/bucket/meta?container=".concat(encodeURI(container)),
document.location.origin,
);

let ret = await fetch(
url,
{
method: "POST",
credentials: "same-origin",
body: JSON.stringify(metadata),
},
);
return ret;
}

export async function getObjects (container) {
// Fetch objects contained in a container from the API for the user
// that's currently logged in.
Expand Down Expand Up @@ -227,21 +261,29 @@ export async function getSharedContainerAddress () {

export async function swiftCreateContainer (
container,
tags,
) {
// Create a container matching the specified name.
let fetchURL = new URL( "/api/containers/".concat(
container,
), document.location.origin);

let body = {
tags,
};
let ret = await fetch(
fetchURL, { method: "PUT", credentials: "same-origin" },
fetchURL, {
method: "PUT",
credentials: "same-origin",
body: JSON.stringify(body),
},
);
if (ret.status != 201) {
if (ret.status == 409) {
throw new Error("Container name already in use.");
}
if (ret.status == 400) {
throw new Error("Invalid container name");
throw new Error("Invalid container or tag name");
}
throw new Error("Container creation not successful.");
}
Expand Down
15 changes: 15 additions & 0 deletions swift_browser_ui_frontend/src/common/conv.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@

import {
getBucketMeta,
} from "./api";

export default function getLangCookie () {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + "OBJ_UI_LANG" + "=([^;]*)",
Expand Down Expand Up @@ -51,3 +56,13 @@ export function getHumanReadableSize (val) {
}
return ret;
}

export async function getTagsForContainer(containerName) {
let tags = [];
await getBucketMeta(containerName).then(meta => {
if ("usertags" in meta[1]) {
tags = meta[1]["usertags"].split(";");
}
});
return tags;
}
12 changes: 12 additions & 0 deletions swift_browser_ui_frontend/src/common/lang.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ let default_translations = {
created: "Created",
folderDetails: "No details for folders",
clearChecked: "Clear checked",
showTags: "Display tags",
},
discover: {
sync_shares: "Synchronize shared buckets",
Expand Down Expand Up @@ -185,12 +186,15 @@ let default_translations = {
copy: " Copy",
create: "Create",
delete: "Delete",
edit: "Edit",
save: "Save",
createContainerButton: "Create Bucket",
copysuccess: "Started copying the bucket in the background",
copyfail: "Failed to copy the bucket",
renderFolders: "Render as Folders",
container_ops: {
addContainer: "Add a new bucket",
editContainer: "Editing bucket: ",
deleteConfirm: "Delete Bucket",
deleteConfirmMessage: "Are you sure you want to delete this " +
"bucket?",
Expand All @@ -199,6 +203,8 @@ let default_translations = {
containerMessage: "The name of the new bucket",
fullDelete: "Deleting a bucket with contents requires deleting " +
"all objects inside it first.",
tagName: "Tags",
tagMessage: "Press enter to add.",
},
objects: {
deleteConfirm: "Delete Objects",
Expand Down Expand Up @@ -286,6 +292,7 @@ let default_translations = {
created: "Luotu",
folderDetails: "Ei yksityiskohtia kansioille",
clearChecked: "Poista valinnat",
showTags: "Näytä Tägit",
},
discover: {
sync_shares: "Synkronoi jaetut säiliöt",
Expand Down Expand Up @@ -409,18 +416,23 @@ let default_translations = {
copy: " Kopioi",
create: "Luo",
delete: "Poista",
edit: "Muokkaa",
save: "Tallenna",
createContainerButton: "Luo säiliö",
copysuccess: "Aloitettiin säiliön kopiointi taustalla",
copyfail: "Säiliön kopiointi epäonnistui",
renderFolders: "Näytä kansioina",
container_ops: {
addContainer: "Luo uusi säiliö",
editContainer: "Muokataan säiliötä: ",
deleteConfirm: "Poista säiliö",
deleteConfirmMessage: "Haluatko varmasti poistaa tämän säiliön?",
deleteSuccess: "Säiliö poistettu",
containerName: "Säiliö",
containerMessage: "Uuden säiliön nimi",
fullDelete: "Säiliön sisältö on poistettava ennen säiliön postamista.",
tagName: "Tägit",
tagMessage: "Paina 'enter' lisätäksesi.",
},
objects: {
deleteConfirm: "Poista objektit",
Expand Down
5 changes: 5 additions & 0 deletions swift_browser_ui_frontend/src/common/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export default new Router({
name: "AddContainer",
component: CreateContainer,
},
{
path: "/browse/:user/:project/:container/edit",
name: "EditContainer",
component: CreateContainer,
},
{
path: "/browse/:user/:project/sharing/requestdirect",
name: "DirectRequest",
Expand Down
57 changes: 44 additions & 13 deletions swift_browser_ui_frontend/src/common/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import Vue from "vue";
import Vuex from "vuex";

import { getBuckets } from "@/common/api";
import {
import {
getObjects,
getSharedObjects,
} from "./api";
import {
getTagsForContainer,
} from "./conv";

Vue.use(Vuex);

Expand All @@ -20,6 +23,7 @@ const store = new Vuex.Store({
isFullPage: true,
objectCache: [],
containerCache: [],
containerTagsCache: {}, // {"containerName": ["tag1", "tag2"]}
langs: [
{ph: "In English", value: "en"},
{ph: "Suomeksi", value: "fi"},
Expand All @@ -33,18 +37,12 @@ const store = new Vuex.Store({
altContainer: undefined,
},
mutations: {
updateContainers (state) {
loading(state, payload) {
state.isLoading = payload;
},
updateContainers (state, payload) {
// Update container cache with the new container listing.
state.isLoading = true;
getBuckets().then((ret) => {
if (ret.status != 200) {
state.isLoading = false;
}
state.containerCache = ret;
state.isLoading = false;
}).catch(() => {
state.isLoading = false;
});
state.containerCache = payload;
},
updateObjects (
state,
Expand Down Expand Up @@ -89,7 +87,13 @@ const store = new Vuex.Store({
});
}
},
eraseObjects (state) {
updateContainerTags(state, payload) {
state.containerTagsCache = {
...state.containerTagsCache,
[payload.containerName]: payload.tags,
};
},
eraseObjects(state) {
state.objectCache = [];
},
setProjects (state, newProjects) {
Expand Down Expand Up @@ -146,6 +150,33 @@ const store = new Vuex.Store({
state.altContainer = undefined;
},
},
actions: {
updateContainers: async function ({ commit, dispatch }) {
commit("loading", true);
let containers = [];
await getBuckets().then((ret) => {
if (ret.status != 200) {
commit("loading", false);
}
containers = ret;
commit("updateContainers", ret);
commit("loading", false);
}).catch(() => {
commit("loading", false);
});
dispatch("updateContainerTags", containers);
return containers;
},
updateContainerTags: function ({ commit }, containers) {
containers.map(async container => {
const tags = await getTagsForContainer(container.name);
commit(
"updateContainerTags",
{containerName: container.name, tags},
);
});
},
},
});

export default store;
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default {
type: "is-success",
});
swiftDeleteContainer(this.container).then(() => {
this.$store.commit("updateContainers");
this.$store.dispatch("updateContainers");
});
},
},
Expand Down
2 changes: 1 addition & 1 deletion swift_browser_ui_frontend/src/entries/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ new Vue({
endUpload: function () {
this.$store.commit("eraseAltContainer");
this.$store.commit("stopUploading");
this.$store.commit("updateContainers");
this.$store.dispatch("updateContainers");
window.onbeforeunload = undefined;
},
startChunking: function () {
Expand Down
Loading