Skip to content
Snippets Groups Projects
Commit b17a1071 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Merge branch 'feature/79-fetch-s3-object-meta-information-dynamically' into 'development'

Resolve "Fetch S3 Object Meta information dynamically"

Closes #79

See merge request !75
parents 3d781e7c 06fd123e
No related branches found
No related tags found
2 merge requests!84Remove development branch,!75Resolve "Fetch S3 Object Meta information dynamically"
Pipeline #39802 passed
......@@ -27,7 +27,7 @@ onBeforeMount(() => {
err.response.data.detail?.includes("JWT")
) {
store.logout();
cookies.remove("bearer", undefined, window.location.hostname);
cookies.remove("bearer");
router.push({
name: "login",
query: {
......
......@@ -17,7 +17,7 @@ const route = useRoute();
function logout() {
store.logout();
cookies.remove("bearer", undefined, window.location.hostname);
cookies.remove("bearer");
}
const activeRoute = ref("");
......
<script setup lang="ts">
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import type { _Object as S3Object } from "@aws-sdk/client-s3";
import dayjs from "dayjs";
import { filesize } from "filesize";
import { computed, onMounted, watch, reactive } from "vue";
import { useS3ObjectStore } from "@/stores/s3objects";
const objectRepository = useS3ObjectStore();
const props = defineProps<{
modalID: string;
s3Object: S3Object;
objectKey: string | undefined;
bucket: string;
}>();
const detailState = reactive<{
loading: boolean;
error: boolean;
}>({
loading: true,
error: false,
});
const metaIdentifier = computed<string>(() => {
return props.bucket + "/" + props.objectKey;
});
watch(props, (newProps) => {
detailState.loading = true;
if (newProps.objectKey) {
fetchMetaInfo(newProps.bucket, newProps.objectKey);
}
});
function fetchMetaInfo(bucket: string, key: string) {
detailState.loading = true;
detailState.error = false;
objectRepository
.fetchS3ObjectMeta(bucket, key, () => {
detailState.loading = false;
})
.catch(() => {
detailState.error = true;
});
}
onMounted(() => {
if (props.objectKey) {
fetchMetaInfo(props.bucket, props.objectKey);
}
});
</script>
<template>
......@@ -30,32 +70,63 @@ const props = defineProps<{
</tr>
<tr>
<th scope="row">Name</th>
<td>{{ props.s3Object.Key }}</td>
<td>{{ props.objectKey }}</td>
</tr>
<tr>
<th scope="row">Size</th>
<td>
<td v-if="detailState.error">N/A</td>
<td v-else-if="detailState.loading" class="placeholder-glow">
<span class="placeholder col-2"></span>
</td>
<td v-else>
{{
filesize(props.s3Object.Size ?? 0, {
base: 2,
standard: "jedec",
})
filesize(
objectRepository.objectMetaMapping[metaIdentifier]
.ContentLength ?? 0,
{
base: 2,
standard: "jedec",
},
)
}}
</td>
</tr>
<tr>
<th scope="row">Timestamp</th>
<td>
<td v-if="detailState.error">N/A</td>
<td v-else-if="detailState.loading" class="placeholder-glow">
<span class="placeholder col-6"></span>
</td>
<td v-else>
{{
dayjs(props.s3Object.LastModified).format(
"YYYY-MM-DD HH:mm:ss",
)
dayjs(
objectRepository.objectMetaMapping[metaIdentifier]
.LastModified,
).format("YYYY-MM-DD HH:mm:ss")
}}
</td>
</tr>
<tr>
<th scope="row">Content Type</th>
<td>binary/octet-stream</td>
<td v-if="detailState.error">N/A</td>
<td v-else-if="detailState.loading" class="placeholder-glow">
<span class="placeholder col-5"></span>
</td>
<td v-else>
{{
objectRepository.objectMetaMapping[metaIdentifier].ContentType
}}
</td>
</tr>
<tr>
<th scope="row">ETag</th>
<td v-if="detailState.error">N/A</td>
<td v-else-if="detailState.loading" class="placeholder-glow">
<span class="placeholder col-10"></span>
</td>
<td v-else>
{{ objectRepository.objectMetaMapping[metaIdentifier].ETag }}
</td>
</tr>
</tbody>
</table>
......
......@@ -28,6 +28,13 @@ import "@fortawesome/fontawesome-free/css/brands.css";
import "./assets/main.css";
import { globalCookiesConfig } from "vue3-cookies";
globalCookiesConfig({
expireTimes: "8d",
domain: window.location.hostname,
});
const app = createApp(App);
app.use(createPinia());
......
import { defineStore } from "pinia";
import type { _Object as S3Object } from "@aws-sdk/client-s3";
import type { _Object as S3Object, HeadObjectOutput } from "@aws-sdk/client-s3";
import {
CopyObjectCommand,
GetObjectCommand,
HeadObjectCommand,
PutObjectCommand,
S3Client,
} from "@aws-sdk/client-s3";
......@@ -24,6 +25,7 @@ export const useS3ObjectStore = defineStore({
state: () =>
({
objectMapping: {},
objectMetaMapping: {},
client: new S3Client({
region: "us-east-1",
endpoint: environment.S3_URL,
......@@ -35,14 +37,19 @@ export const useS3ObjectStore = defineStore({
}),
}) as {
objectMapping: Record<string, S3Object[]>;
objectMetaMapping: Record<string, HeadObjectOutput>;
client: S3Client;
},
getters: {
getPresignedUrl(): (bucketName: string, key: string) => Promise<string> {
return (bucketName, key) => {
const keySplit = key.split("/");
const command = new GetObjectCommand({
Bucket: bucketName,
Key: key,
ResponseContentDisposition: `attachment; filename=${
keySplit[keySplit.length - 1]
}`,
});
return getSignedUrl(this.client, command, {
expiresIn: 30,
......@@ -99,6 +106,27 @@ export const useS3ObjectStore = defineStore({
}
return objs;
},
fetchS3ObjectMeta(
bucketName: string,
key: string,
onFinally?: () => void,
): Promise<HeadObjectOutput> {
const identifier = bucketName + "/" + key;
if (this.objectMetaMapping[identifier]) {
onFinally?.();
}
const command = new HeadObjectCommand({
Bucket: bucketName,
Key: key,
});
return this.client
.send(command)
.then((resp) => {
this.objectMetaMapping[identifier] = resp;
return resp;
})
.finally(onFinally);
},
deleteObject(bucketName: string, key: string): Promise<void> {
const command = new DeleteObjectCommand({
Bucket: bucketName,
......
......@@ -58,7 +58,7 @@ const objectState = reactive<{
bucketPermissionError: boolean;
editObjectKey: string;
copyObject: S3Object;
viewDetailObject: S3Object;
viewDetailKey: string | undefined;
}>({
loading: true,
filterString: "",
......@@ -70,11 +70,7 @@ const objectState = reactive<{
Size: 0,
LastModified: new Date(),
},
viewDetailObject: {
Key: "",
Size: 0,
LastModified: new Date(),
},
viewDetailKey: undefined,
});
// Computed Properties
......@@ -232,6 +228,7 @@ watch(
() => props.bucketName,
(newBucketName, oldBucketName) => {
if (oldBucketName !== newBucketName) {
objectState.viewDetailKey = undefined;
// If bucket is changed, update the objects
objectState.bucketPermissionError = false;
objectState.bucketNotFoundError = false;
......@@ -735,7 +732,7 @@ function getObjectFileName(key: string): string {
type="button"
data-bs-toggle="modal"
data-bs-target="#detail-object-modal"
@click="objectState.viewDetailObject = obj"
@click="objectState.viewDetailKey = obj.Key"
>
Details
</button>
......@@ -814,7 +811,7 @@ function getObjectFileName(key: string): string {
/>
<object-detail-modal
:bucket="bucketName"
:s3-object="objectState.viewDetailObject"
:object-key="objectState.viewDetailKey"
modalID="detail-object-modal"
/>
</div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment