Squashed 'components/rfs/' content from commit 9808a5e
git-subtree-dir: components/rfs git-subtree-split: 9808a5e9fc768edc7d8b1dfa5b91b3f018dff0cb
This commit is contained in:
1
frontend/.env
Normal file
1
frontend/.env
Normal file
@@ -0,0 +1 @@
|
||||
VITE_API_URL="http://localhost:4000"
|
||||
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
frontend/.vscode/extensions.json
vendored
Normal file
3
frontend/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
13
frontend/Dockerfile
Normal file
13
frontend/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
# build stage
|
||||
FROM node:lts-alpine as build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# production stage
|
||||
FROM nginx:stable-alpine as production-stage
|
||||
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
82
frontend/README.md
Normal file
82
frontend/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Threefold RFS
|
||||
|
||||
## Description
|
||||
|
||||
`Threefold RFS` is a frontend that helps manage the RFS server for creating, mounting, and extracting FungiStore lists, or fl for short. An fl is a simple format that stores information about a whole filesystem in a compact way. It doesn't hold the actual data but includes enough details to retrieve the data from a store.
|
||||
|
||||
## Prerequesites
|
||||
|
||||
- build essentials
|
||||
|
||||
```bash
|
||||
sudo apt-get install build-essential
|
||||
```
|
||||
|
||||
- [node js](https://nodejs.org/en/download/package-manager)
|
||||
- [rust](https://www.rust-lang.org/tools/install)
|
||||
- Cargo, to be configured to run in the shell
|
||||
- musl tool
|
||||
|
||||
```bash
|
||||
sudo apt install musl-tools
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://github.com/threefoldtech/rfs.git
|
||||
```
|
||||
|
||||
### backend
|
||||
|
||||
In fl-server dir:
|
||||
|
||||
- create flists dir containaing dirs for each user
|
||||
ex:
|
||||
- fl-server
|
||||
- flists
|
||||
- user1
|
||||
- user2
|
||||
- include config file
|
||||
ex:
|
||||
|
||||
```yml
|
||||
host='localhost'
|
||||
port=4000
|
||||
store_url=['dir:///tmp/store0']
|
||||
flist_dir='flists'
|
||||
|
||||
jwt_secret='secret'
|
||||
jwt_expire_hours=5
|
||||
|
||||
[[users]] # list of authorized user in the server
|
||||
username = "user1"
|
||||
password = "password1"
|
||||
|
||||
[[users]]
|
||||
username = "user2"
|
||||
password = "password2"
|
||||
```
|
||||
|
||||
- Move to `fl-server` directory and execute the following command to run the backend:
|
||||
|
||||
```bash
|
||||
cargo run --bin fl-server -- --config-path config.toml
|
||||
```
|
||||
|
||||
### frontend
|
||||
|
||||
- Move to `frontend` directory, open new terminal and execute the following commands to run the frontend:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
- Login with users listed in config.toml with their username and password
|
||||
- Create Flist
|
||||
- Preview Flist
|
||||
- List all Flists
|
||||
- Download Flist
|
||||
18
frontend/index.html
Normal file
18
frontend/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image" href="./src/assets/logo.png">
|
||||
<title>Threefold Flist</title>
|
||||
<link href="./src/style.css" rel="stylesheet">
|
||||
<link href="https://cdn.materialdesignicons.com/5.4.55/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1476
frontend/package-lock.json
generated
Normal file
1476
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
frontend/package.json
Normal file
29
frontend/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"axios": "^1.7.3",
|
||||
"filesize": "^10.1.4",
|
||||
"mdi": "^2.2.43",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.2",
|
||||
"vue3-toastify": "^0.2.2",
|
||||
"vuetify": "^3.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.1.0",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.3.4",
|
||||
"vue-tsc": "^2.0.24"
|
||||
}
|
||||
}
|
||||
1
frontend/public/vite.svg
Normal file
1
frontend/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
33
frontend/src/App.vue
Normal file
33
frontend/src/App.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<Navbar v-if="route.path != `/login`"></Navbar>
|
||||
<v-main class="mn-height" style="--v-layout-left: 0px;" >
|
||||
<div :key="route.path">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</v-main>
|
||||
<Footer v-if="route.path != `/login`"></Footer>
|
||||
</router-view>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Footer from './components/Footer.vue';
|
||||
import Navbar from './components/Navbar.vue';
|
||||
</script>
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
}
|
||||
</style>
|
||||
BIN
frontend/src/assets/Image.png
Normal file
BIN
frontend/src/assets/Image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
0
frontend/src/assets/Image.png:Zone.Identifier
Normal file
0
frontend/src/assets/Image.png:Zone.Identifier
Normal file
BIN
frontend/src/assets/home.png
Normal file
BIN
frontend/src/assets/home.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
0
frontend/src/assets/home.png:Zone.Identifier
Normal file
0
frontend/src/assets/home.png:Zone.Identifier
Normal file
BIN
frontend/src/assets/logo.png
Normal file
BIN
frontend/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
0
frontend/src/assets/logo.png:Zone.Identifier
Normal file
0
frontend/src/assets/logo.png:Zone.Identifier
Normal file
BIN
frontend/src/assets/logo_white.png
Normal file
BIN
frontend/src/assets/logo_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
0
frontend/src/assets/logo_white.png:Zone.Identifier
Normal file
0
frontend/src/assets/logo_white.png:Zone.Identifier
Normal file
BIN
frontend/src/assets/side.png
Normal file
BIN
frontend/src/assets/side.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 336 KiB |
0
frontend/src/assets/side.png:Zone.Identifier
Normal file
0
frontend/src/assets/side.png:Zone.Identifier
Normal file
11
frontend/src/client.ts
Normal file
11
frontend/src/client.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + sessionStorage.getItem("token"),
|
||||
},
|
||||
});
|
||||
308
frontend/src/components/CreateFlist.vue
Normal file
308
frontend/src/components/CreateFlist.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<div class="d-flex flex-column justify-center mt-10" >
|
||||
<v-container fluid>
|
||||
<v-row justify="center">
|
||||
<v-col cols="8">
|
||||
<h2 class="mb-2">Create a Flist:</h2>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-form>
|
||||
<v-row justify="center">
|
||||
<v-col cols="8">
|
||||
<label
|
||||
for="image-name"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center"
|
||||
>
|
||||
Image Name<span style="color: red">*</span>
|
||||
</label>
|
||||
|
||||
<v-text-field
|
||||
class="pr-5 rounded"
|
||||
id="image-name"
|
||||
v-model="flist.image_name"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
required
|
||||
placeholder="example: redis, keinos/sqlite3, alpine"
|
||||
>
|
||||
</v-text-field>
|
||||
<v-checkbox
|
||||
value="true"
|
||||
v-model="privateReg"
|
||||
hide-details
|
||||
density="compact"
|
||||
><template v-slot:label>
|
||||
<span class="text-subtitle-2">Private Registery</span>
|
||||
<v-tooltip activator="parent" location="start">Check this box to pull the Docker image from your private registry instead of the public repository.</v-tooltip>
|
||||
</template>
|
||||
</v-checkbox>
|
||||
|
||||
<div v-if="privateReg">
|
||||
<v-alert text="Select a sign-in method" type="info" density="compact" color = "#1aa18f" closable width="60em"></v-alert>
|
||||
<v-radio-group class="p-0 m-0" v-model="privateType" inline>
|
||||
<v-radio value="username">
|
||||
<template v-slot:label>
|
||||
<span class="text-subtitle-2">Username - Password</span>
|
||||
</template>
|
||||
</v-radio>
|
||||
<v-radio value="email">
|
||||
<template v-slot:label>
|
||||
<span class="text-subtitle-2">Email - Password</span>
|
||||
</template>
|
||||
</v-radio>
|
||||
<v-radio value="token">
|
||||
<template v-slot:label>
|
||||
<span class="text-subtitle-2">Identity Token</span>
|
||||
<v-tooltip activator="parent" location="bottom">Token you can as an alternative to your email/username password</v-tooltip>
|
||||
</template>
|
||||
</v-radio>
|
||||
</v-radio-group>
|
||||
<v-container class="pr-0 pl-0">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div v-if="privateType === `email`">
|
||||
<label
|
||||
for="email"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<v-text-field
|
||||
class="pr-5 rounded"
|
||||
id="email"
|
||||
v-model="flist.email"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
placeholder="johndoe@gmail.com"
|
||||
type="email"
|
||||
>
|
||||
</v-text-field>
|
||||
</div>
|
||||
<div v-if="privateType !== `email`">
|
||||
<label
|
||||
for="username"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<v-text-field
|
||||
class="pr-5 text-medium-emphasis"
|
||||
id="username"
|
||||
v-model="flist.username"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
:placeholder="
|
||||
privateType === `token` ? `token` : `johndoe`
|
||||
"
|
||||
:value="privateType === `token`?`token`:``"
|
||||
:readonly="privateType === `token`"
|
||||
>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<div
|
||||
v-if="privateType.length != 0 && privateType !== `token`"
|
||||
>
|
||||
<label
|
||||
for="password"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<v-text-field
|
||||
class="pr-5 rounded"
|
||||
id="password"
|
||||
v-model="flist.password"
|
||||
variant="solo-filled"
|
||||
:append-inner-icon="visible ? 'mdi-eye-off' : 'mdi-eye'"
|
||||
:type="visible ? 'text' : 'password'"
|
||||
@click:append-inner="visible = !visible"
|
||||
density="compact"
|
||||
>
|
||||
</v-text-field>
|
||||
</div>
|
||||
<div v-if="privateType === `token`">
|
||||
<label
|
||||
for="identity-token"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Identity Token
|
||||
</label>
|
||||
<v-text-field
|
||||
class="pr-5 rounded"
|
||||
id="identity-token"
|
||||
v-model="flist.identity_token"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
|
||||
<v-checkbox
|
||||
value="true"
|
||||
v-model="registeryAddress"
|
||||
hide-details
|
||||
density="compact"
|
||||
><template v-slot:label>
|
||||
<span class="text-subtitle-2">Self Hosted Registery</span>
|
||||
<v-tooltip activator="parent" location="start">Check this box to pull the Docker image from your self-hosted registry using registery address</v-tooltip>
|
||||
|
||||
</template>
|
||||
</v-checkbox>
|
||||
<div v-if="registeryAddress">
|
||||
<label
|
||||
for="server-address"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Registery Address
|
||||
</label>
|
||||
<v-text-field
|
||||
class="pr-5 rounded"
|
||||
id="server-address"
|
||||
v-model="flist.server_address"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
placeholder="localhost:5000"
|
||||
>
|
||||
</v-text-field>
|
||||
</div>
|
||||
<v-checkbox
|
||||
value="true"
|
||||
v-model="registeryToken"
|
||||
density="compact"
|
||||
hide-details
|
||||
><template v-slot:label>
|
||||
<span class="text-subtitle-2">Web Registery Token</span>
|
||||
<v-tooltip activator="parent" location="start">Check this box to use web registry token to pull image from your registry with secure authentication</v-tooltip>
|
||||
|
||||
</template>
|
||||
</v-checkbox>
|
||||
<div v-if="registeryToken">
|
||||
<label
|
||||
for="registery-token"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Registery Token
|
||||
</label>
|
||||
<v-text-field
|
||||
class="pr-5 rounded mb-5"
|
||||
id="registery-token"
|
||||
v-model="flist.registry_token"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col offset="8" class="pa-0">
|
||||
<div class="position-relative" style="left: -5%;" >
|
||||
<v-btn
|
||||
class="pr-5 rounded-pill background-green mb-8 mt-5 text-white"
|
||||
size="large"
|
||||
width="50%"
|
||||
@click="create"
|
||||
:disabled="pending"
|
||||
v-if = "!pending"
|
||||
>
|
||||
Create
|
||||
</v-btn>
|
||||
<v-progress-linear
|
||||
:size="70"
|
||||
color="#1aa18f"
|
||||
indeterminate
|
||||
class="mb-5 mt-5 w-50"
|
||||
rounded=""
|
||||
height="20"
|
||||
v-else
|
||||
>
|
||||
<template v-slot:default> {{ progress }} % </template>
|
||||
</v-progress-linear>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { Flist } from "../types/Flist";
|
||||
|
||||
import { toast } from "vue3-toastify";
|
||||
import "vue3-toastify/dist/index.css";
|
||||
import { api } from "../client";
|
||||
import router from "../router";
|
||||
|
||||
const pending = ref<boolean>(false);
|
||||
let progress = ref<number>(0);
|
||||
const stopPolling = ref<boolean>(false);
|
||||
let polling: NodeJS.Timeout;
|
||||
let id = ""
|
||||
const pullLists = async () => {
|
||||
try {
|
||||
const response = await api.get("v1/api/fl/" + id);
|
||||
if (response.data.flist_state.InProgress) {
|
||||
progress.value = Math.floor(
|
||||
response.data.flist_state.InProgress.progress
|
||||
);
|
||||
} else {
|
||||
stopPolling.value = true;
|
||||
pending.value = false;
|
||||
router.push({name: "myflists"})
|
||||
}
|
||||
} catch (error: any) {
|
||||
pending.value = false;
|
||||
stopPolling.value = true;
|
||||
toast.error(error.response?.data)
|
||||
}
|
||||
};
|
||||
|
||||
watch(stopPolling, () => {
|
||||
if (stopPolling.value) {
|
||||
clearInterval(polling);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
const privateReg = ref<boolean>(false);
|
||||
const registeryAddress = ref<boolean>(false);
|
||||
const registeryToken = ref<boolean>(false);
|
||||
const privateType = ref<string>("username");
|
||||
|
||||
const flist = ref<Flist>({
|
||||
auth: "",
|
||||
email: "",
|
||||
identity_token: "",
|
||||
image_name: "",
|
||||
password: "",
|
||||
registry_token: "",
|
||||
server_address: "",
|
||||
username: "",
|
||||
});
|
||||
const visible = ref<boolean>(false);
|
||||
const create = async () => {
|
||||
try {
|
||||
const response = await api.post("/v1/api/fl", flist.value);
|
||||
id = response.data.id
|
||||
pending.value = true
|
||||
polling = setInterval(pullLists, 1 * 10000);
|
||||
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data || "error occured");
|
||||
const errors: Number[] = [401, 403];
|
||||
if (errors.includes(error.response?.status)) {
|
||||
sessionStorage.removeItem("token");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
23
frontend/src/components/Footer.vue
Normal file
23
frontend/src/components/Footer.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<v-footer class="bg-grey-darken-3 d-flex justify-center w-100 m-0">
|
||||
All rights reserved © 2024 -
|
||||
<a
|
||||
href="https://threefold.io"
|
||||
style="color: inherit; text-decoration: none"
|
||||
>
|
||||
ThreeFold <v-icon icon="mdi-link" style="font-size: 1em" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/threefoldtech"
|
||||
style="color: inherit; text-decoration: none"
|
||||
>
|
||||
<v-icon icon="mdi-github" style="margin-left: 7px" />
|
||||
</a>
|
||||
</v-footer>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.v-footer {
|
||||
height: 7% !important;
|
||||
}
|
||||
</style>
|
||||
158
frontend/src/components/Home.vue
Normal file
158
frontend/src/components/Home.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template >
|
||||
<div class="w-100 position-relative" style="top: -62.5px" >
|
||||
<v-img :src="image" cover style="z-index: 2"></v-img>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-center mt-0">
|
||||
<v-navigation-drawer
|
||||
app
|
||||
class="position-absolute mx-height"
|
||||
style="top: 30%; left: 0; height: 62.5%; width: fit-content; min-width: 12.5%;"
|
||||
>
|
||||
<v-list>
|
||||
<v-list-item nav>
|
||||
<v-list-item-title class=" text-h6 " > Users</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item density="compact"
|
||||
v-for="userName in userNameList"
|
||||
:key="userName"
|
||||
@click="username = userName"
|
||||
>
|
||||
<template v-slot:prepend >
|
||||
<v-icon icon="mdi-account" color="#1aa18f" style="font-size: 15px;"></v-icon>
|
||||
<v-list-item-title style="padding: 2px 4px;
|
||||
font-size: 15px;
|
||||
font-weight: 300;">
|
||||
{{ userName }}
|
||||
</v-list-item-title>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-container
|
||||
class="d-flex flex-column w-75 "
|
||||
fluid
|
||||
style="height: fit-content; position: relative; left: 6%;"
|
||||
>
|
||||
<h2 class="mb-2" v-if="username.length != 0">
|
||||
<v-icon icon="mdi-account" color="#1aa18f"></v-icon>{{ username }}
|
||||
</h2>
|
||||
<!-- table containe flists -->
|
||||
<v-data-table density="compact"
|
||||
:items="filteredFlist"
|
||||
:headers="tableHeader"
|
||||
dense
|
||||
|
||||
class="thick-border "
|
||||
items-per-page="25"
|
||||
>
|
||||
<template #item.name="{ value }">
|
||||
<v-icon icon="mdi-text-box" class="mr-1" color="grey"/>
|
||||
<span class="file-name">{{ value }}</span>
|
||||
</template>
|
||||
<template v-slot:item.preview = "{index}" >
|
||||
<a :href="`/` + filteredFlist[index].path_uri">
|
||||
<v-btn class="elevation-0">
|
||||
<v-icon icon="mdi-eye-outline" color="grey"></v-icon>
|
||||
</v-btn>
|
||||
</a>
|
||||
</template>
|
||||
<template #item.size="{value}">
|
||||
{{filesize(value, {standard: "jedec", precision: 3})}}
|
||||
</template>
|
||||
<template #item.last_modified="{ value }">
|
||||
{{ new Date(value * 1000).toString().split("(")[0] }}
|
||||
</template>
|
||||
<template #item.path_uri="{ value }">
|
||||
<v-btn class="elevation-0">
|
||||
<a :href="baseURL + `/` + value" download>
|
||||
<v-icon icon="mdi-download" color="grey"></v-icon
|
||||
></a>
|
||||
<v-tooltip activator="parent" location="start"
|
||||
>Download flist</v-tooltip
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn @click="copyLink(baseURL + `/` + value)" class="elevation-0">
|
||||
<v-icon icon="mdi-content-copy" color="grey"></v-icon>
|
||||
<v-tooltip activator="parent">Copy Link</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import image from "../assets/home.png";
|
||||
import { FlistsResponseInterface, FlistBody } from "../types/Flist.ts";
|
||||
import { toast } from "vue3-toastify";
|
||||
import "vue3-toastify/dist/index.css";
|
||||
import { api } from "../client.ts";
|
||||
import { copyLink } from "../helpers.ts";
|
||||
import {filesize} from "filesize";
|
||||
|
||||
const baseURL = import.meta.env.VITE_API_URL;
|
||||
|
||||
|
||||
const tableHeader = [
|
||||
{ title: "File Name", key: "name" },
|
||||
{ title: "Preview", key:"preview"},
|
||||
{ title: "Size", key: "size" },
|
||||
{ title: "Last Modified", key: "last_modified" },
|
||||
{ title: "Download", key: "path_uri", sortable: false },
|
||||
];
|
||||
var flists = ref<FlistsResponseInterface>({});
|
||||
const username = ref("");
|
||||
const userNameList = ref<string[]>([]);
|
||||
let filteredFlist = ref<FlistBody[]>([]);
|
||||
const filteredFlistFn = () => {
|
||||
filteredFlist.value = [];
|
||||
const map = flists.value;
|
||||
if (username.value.length === 0) {
|
||||
for (var flistMap in map) {
|
||||
for (let flist of map[flistMap]) {
|
||||
if (flist.progress === 100) {
|
||||
filteredFlist.value.push(flist);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let flist of map[username.value]) {
|
||||
if (flist.progress === 100) {
|
||||
filteredFlist.value.push(flist);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const getUserNames = () => {
|
||||
const list: string[] = [];
|
||||
const map = flists.value;
|
||||
for (var flistMap in map) {
|
||||
list.push(flistMap);
|
||||
}
|
||||
userNameList.value = list;
|
||||
};
|
||||
onMounted(async () => {
|
||||
try {
|
||||
flists.value = (await api.get<FlistsResponseInterface>("/v1/api/fl")).data;
|
||||
getUserNames();
|
||||
filteredFlistFn();
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data);
|
||||
}
|
||||
});
|
||||
watch(username, () => {
|
||||
filteredFlistFn();
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.mx-height {
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
.mn-height {
|
||||
min-height: calc(100% - 37%);
|
||||
}
|
||||
</style>
|
||||
113
frontend/src/components/Login.vue
Normal file
113
frontend/src/components/Login.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<v-container fluid class="overflow-hidden pa-0" style="height: 100vh;">
|
||||
<v-row class="h-100 ma-0 pa-0">
|
||||
<v-col :cols="4" class="position-relative ma-0 pa-0 h-100">
|
||||
<v-img :src="image" cover height="100%" style="z-index: 900"> </v-img>
|
||||
<v-container
|
||||
class="position-absolute top-0 d-flex flex-column justify-center ga-0"
|
||||
style="z-index: 1000; height: 70%"
|
||||
>
|
||||
<v-img
|
||||
:src="whiteLogo"
|
||||
height="10%"
|
||||
width="15%"
|
||||
class="mb-5 flex-grow-0"
|
||||
></v-img>
|
||||
<p class="mt-0 text-white" style="width: 90%">
|
||||
FungiStore is the main tool to create, mount, and extract FungiStore lists (Fungilist or FL for short). An FL is a simple format used to store information about an entire filesystem in a compact form. It does not contain the data itself but provides enough information to retrieve this data from a store.
|
||||
</p>
|
||||
</v-container>
|
||||
</v-col>
|
||||
<v-col :cols="8" class="d-flex align-center">
|
||||
<v-container class="d-flex flex-column align-center justify-center">
|
||||
<v-col :cols="6">
|
||||
<v-form>
|
||||
<v-img :src="logo" class="mb-10" height="10%" width="15%"></v-img>
|
||||
<h2 class="mb-5">Sign in</h2>
|
||||
|
||||
<label
|
||||
for="username"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<v-text-field
|
||||
class="pr-5 rounded"
|
||||
v-model="user.username"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
id="username"
|
||||
required
|
||||
>
|
||||
</v-text-field>
|
||||
<label
|
||||
for="password"
|
||||
class="text-subtitle-1 text-medium-emphasis d-flex align-center justify-space-between"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<v-text-field
|
||||
class="mb-5 pr-5 rounded"
|
||||
v-model="user.password"
|
||||
:append-inner-icon="visible ? 'mdi-eye-off' : 'mdi-eye'"
|
||||
:type="visible ? 'text' : 'password'"
|
||||
variant="outlined"
|
||||
@click:append-inner="visible = !visible"
|
||||
density="compact"
|
||||
id="password"
|
||||
required
|
||||
>
|
||||
</v-text-field>
|
||||
<v-btn
|
||||
class="pr-5 rounded-pill background-green text-white position-relative"
|
||||
style="left: 205px;"
|
||||
size="large"
|
||||
width="50%"
|
||||
:disabled="loading"
|
||||
@click="login"
|
||||
>Sign In</v-btn>
|
||||
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-container>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import image from "./../assets/side.png";
|
||||
import logo from "./../assets/logo.png";
|
||||
import whiteLogo from "../assets/logo_white.png";
|
||||
import { User } from "../types/User.ts";
|
||||
import { api } from "../client.ts";
|
||||
import { toast } from "vue3-toastify";
|
||||
import "vue3-toastify/dist/index.css";
|
||||
import router from "../router/index.ts";
|
||||
|
||||
|
||||
|
||||
const user = ref<User>({ username: "", password: "" });
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
|
||||
const visible = ref<boolean>(false);
|
||||
const login = async () => {
|
||||
try {
|
||||
const response = await api.post("/v1/api/signin", user.value);
|
||||
const token = response.data.access_token;
|
||||
sessionStorage.setItem("token", token);
|
||||
sessionStorage.setItem("username", user.value.username);
|
||||
api.interceptors.request.use((config) => {
|
||||
if (token) {
|
||||
config.headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
router.push("/myflists")
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data || "error occured");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
63
frontend/src/components/Navbar.vue
Normal file
63
frontend/src/components/Navbar.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<v-app-bar color="#1aa18f">
|
||||
<v-app-bar-nav-icon to="/" class="ml-8">
|
||||
<v-img :src="whiteLogo" contain height="50px" width="50px"></v-img>
|
||||
</v-app-bar-nav-icon>
|
||||
<v-spacer> </v-spacer>
|
||||
<div class="mr-5" v-if="auth === null || auth?.length === 0">
|
||||
<v-btn to="login">Login</v-btn>
|
||||
</div>
|
||||
<div class="mr-5" v-else>
|
||||
<v-btn to="create"
|
||||
><v-icon icon="mdi-plus-circle-outline" class="mr-2"></v-icon>Create
|
||||
flist</v-btn
|
||||
>
|
||||
<v-menu class="white">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
class="align-self-center me-4"
|
||||
height="100%"
|
||||
rounded="50%"
|
||||
variant="plain"
|
||||
v-bind="props"
|
||||
style="font-size: 20px"
|
||||
>
|
||||
<v-icon icon="mdi-account"></v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-btn><a href="/myflists" class="text-black" style="text-decoration:none;">My FLists</a></v-btn>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-btn @click="logout"
|
||||
><v-icon icon="mdi-logout" style="font-size: 20px" />log
|
||||
out</v-btn
|
||||
>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import whiteLogo from "../assets/logo_white.png";
|
||||
import { toast } from "vue3-toastify";
|
||||
import router from "../router";
|
||||
|
||||
const auth= ref<string|null>(sessionStorage.getItem("token"));
|
||||
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
sessionStorage.removeItem("token")
|
||||
sessionStorage.removeItem("username")
|
||||
auth.value = sessionStorage.getItem("token");
|
||||
router.push("/")
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data || "error occured");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
140
frontend/src/components/PreviewFlist.vue
Normal file
140
frontend/src/components/PreviewFlist.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="w-100 position-relative" style="top: -62.5px">
|
||||
<v-img :src="image" cover style="z-index: 2"></v-img>
|
||||
<div
|
||||
class="position-absolute w-100 text-white d-flex justify-content align-content "
|
||||
style="z-index: 4; top: 55%;left:40%;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mn-height mb-10" v-if="!pending">
|
||||
<v-container class="m-0 pa-0">
|
||||
<v-row>
|
||||
<div>
|
||||
<h2 class="text-h4 mb-3">{{
|
||||
id
|
||||
}}</h2>
|
||||
<p>This Flist was created by <v-chip color="#1aa18f" label>{{ username }} </v-chip> </p>
|
||||
</div>
|
||||
</v-row>
|
||||
<v-row class="d-flex flex-column">
|
||||
<h3 class="text-subtitle-1 text-grey-darken-2">Source file</h3>
|
||||
<v-text-field rounded="20" variant="outlined" density="compact" readonly class="text-grey-darken-1 mr-0">
|
||||
{{ baseURL + url }}
|
||||
<template #append>
|
||||
<v-btn
|
||||
color="#1aa18f"
|
||||
value="Copy"
|
||||
class="Btn"
|
||||
@click="copyLink(baseURL + url)">
|
||||
Copy
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-row>
|
||||
<v-row class="d-flex flex-column">
|
||||
<h3 class="text-subtitle-1 text-grey-darken-2">Archive Checksum (MD5)</h3>
|
||||
<v-text-field rounded="20" variant="outlined" density="compact" readonly class="text-grey-darken-1 mr-0">
|
||||
{{flistPreview.checksum}}
|
||||
<template #append>
|
||||
<v-btn
|
||||
color="#1aa18f"
|
||||
value="Copy"
|
||||
class="Btn"
|
||||
@click="copyLink(flistPreview.checksum)">
|
||||
Copy
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-row>
|
||||
<v-row class="d-flex flex-column">
|
||||
<h3 class="text-subtitle-1 text-grey-darken-2">Metadata</h3>
|
||||
<v-text-field rounded="20" variant="outlined" density="compact" readonly class="text-grey-darken-1 mr-0" width="98.5%">
|
||||
{{ flistPreview.metadata}}
|
||||
<template #prepend-inner>
|
||||
<v-chip color="#1aa18f" label class ="chip">Backend (default)</v-chip>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-row>
|
||||
<v-row class="d-flex flex-column">
|
||||
<h3 class="text-subtitle-1 text-grey-darken-2">Content</h3>
|
||||
<v-textarea :model-value="showContent" variant="outlined" readonly rows="1" :class= "linkDecoration" class="text-grey-darken-1" auto-grow width="98.5%" @click="contentShow()">
|
||||
</v-textarea>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
<div class="d-flex align-center justify-center mb-12 mt-12" v-else>
|
||||
<v-progress-circular
|
||||
:size="70"
|
||||
:width="7"
|
||||
color="#1aa18f"
|
||||
indeterminate
|
||||
class="mb-5"
|
||||
>
|
||||
</v-progress-circular>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { onMounted, ref } from "vue";
|
||||
import image from "../assets/home.png";
|
||||
import { toast } from "vue3-toastify";
|
||||
import "vue3-toastify/dist/index.css";
|
||||
import { api } from "../client.ts";
|
||||
import { copyLink } from "../helpers.ts";
|
||||
import { FlistPreview } from "../types/Flist.ts";
|
||||
|
||||
const pending = ref<boolean>(true)
|
||||
const flistPreview = ref<FlistPreview>({checksum:"", content:[], metadata:""});
|
||||
const urlPartition = window.location.href.split("/")
|
||||
const id = ref<string>(urlPartition[urlPartition.length - 1])
|
||||
const username = ref<string>(urlPartition[urlPartition.length - 2])
|
||||
const baseURL = ref<string>(import.meta.env.VITE_API_URL + "/");
|
||||
const url ="flists" + "/" + username.value + "/" + id.value
|
||||
|
||||
const showContent = ref<string>()
|
||||
const linkDecoration = ref<string>("text-as-anchor")
|
||||
const contentShow = () => {
|
||||
showContent.value = flistPreview.value?.content.join("\n")
|
||||
linkDecoration.value = ""
|
||||
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const encodedUrl = url.replaceAll("/", "%2F");
|
||||
flistPreview.value = (await api.get<FlistPreview>("/v1/api/fl/preview/" + encodedUrl)).data;
|
||||
flistPreview.value.content = flistPreview.value.content.slice(1)
|
||||
showContent.value = "show content on click"
|
||||
pending.value = false
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.Btn{
|
||||
position: relative;
|
||||
left: -18px;
|
||||
height: 40px;
|
||||
width: 110px;
|
||||
margin-left:0px;
|
||||
}
|
||||
|
||||
.chip{
|
||||
height: 40px;
|
||||
position: relative;
|
||||
left: -11px;
|
||||
}
|
||||
|
||||
.text-as-anchor {
|
||||
color: #42A5F5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.text-as-anchor:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
129
frontend/src/components/UserFlist.vue
Normal file
129
frontend/src/components/UserFlist.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-container class="pa-0">
|
||||
<v-row no-gutters class="pa-0 ma-0">
|
||||
<div class="user">
|
||||
<h2 class="mt-5 mb-5 text-h5 text-grey-darken-2">
|
||||
<v-icon icon="mdi-account" color="#1aa18f"></v-icon
|
||||
>{{ loggedInUser }}
|
||||
</h2>
|
||||
</div>
|
||||
</v-row>
|
||||
<v-row no-gutters class="pa-0 ma-0">
|
||||
<v-data-table
|
||||
density="compact"
|
||||
v-if="loggedInUser"
|
||||
:items="currentUserFlists"
|
||||
:headers="tableHeader"
|
||||
dense
|
||||
items-per-page="25"
|
||||
class="thick-border"
|
||||
>
|
||||
<template #item.name="{ value }">
|
||||
<v-icon icon="mdi-text-box" class="mr-1" color="grey" />
|
||||
<span class="file-name">{{ value }}</span>
|
||||
</template>
|
||||
<template v-slot:item.preview = "{index}" >
|
||||
<a :href="`/` + currentUserFlists[index].path_uri">
|
||||
<v-btn class="elevation-0">
|
||||
<v-icon icon="mdi-eye-outline" color="grey"></v-icon>
|
||||
</v-btn>
|
||||
</a>
|
||||
</template>
|
||||
<template #item.size="{ value }">
|
||||
{{ filesize(value, { standard: "jedec", precision: 3 }) }}
|
||||
</template>
|
||||
<template v-slot:item.path_uri="{ index, value }">
|
||||
<template v-if="currentUserFlists[index].progress === 100">
|
||||
<v-btn class="elevation-0">
|
||||
<a :href="baseURL + `/` + value" download>
|
||||
<v-icon icon="mdi-download" color="grey"></v-icon
|
||||
></a>
|
||||
<v-tooltip activator="parent" location="start"
|
||||
>Download flist</v-tooltip
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
@click="copyLink(baseURL + `/` + value)"
|
||||
class="elevation-0"
|
||||
>
|
||||
<v-icon icon="mdi-content-copy" color="grey"></v-icon>
|
||||
<v-tooltip activator="parent">Copy Link</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>loading... </span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #item.last_modified="{ value }">
|
||||
{{ new Date(value * 1000).toString().split("(")[0] }}
|
||||
</template>
|
||||
|
||||
<template v-slot:item.progress="{ value }" class="w-25">
|
||||
<template v-if="value != 100">
|
||||
<v-progress-linear
|
||||
:model-value="value"
|
||||
color="#1aa18f"
|
||||
height="20"
|
||||
rounded="sm"
|
||||
>
|
||||
<template v-slot:default="{ value }">
|
||||
<span class="text-white">{{ Math.floor(value) }}%</span>
|
||||
</template>
|
||||
</v-progress-linear>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-chip color="#1aa18f">finished</v-chip>
|
||||
</template>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { FlistsResponseInterface } from "../types/Flist.ts";
|
||||
import { computed } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { toast } from "vue3-toastify";
|
||||
import { api } from "../client.ts";
|
||||
import { copyLink } from "../helpers.ts";
|
||||
import { filesize } from "filesize";
|
||||
|
||||
|
||||
|
||||
|
||||
const tableHeader = [
|
||||
{ title: "File Name", key: "name" },
|
||||
{ title: "Preview", key:"preview"},
|
||||
{ title: "Size", key: "size" },
|
||||
{ title: "Last Modified", key: "last_modified" },
|
||||
{ title: "Download", key: "path_uri", sortable: false },
|
||||
{ title: "Progress", key: "progress", width: "20%" },
|
||||
];
|
||||
const loggedInUser = sessionStorage.getItem("username");
|
||||
var flists = ref<FlistsResponseInterface>({});
|
||||
const baseURL = import.meta.env.VITE_API_URL;
|
||||
let currentUserFlists = computed(() => {
|
||||
return loggedInUser?.length ? flists.value[loggedInUser] : [];
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
flists.value = (await api.get<FlistsResponseInterface>("/v1/api/fl")).data;
|
||||
currentUserFlists = computed(() => {
|
||||
return loggedInUser?.length ? flists.value[loggedInUser] : [];
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user .v-icon--size-default {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
</style>
|
||||
8
frontend/src/helpers.ts
Normal file
8
frontend/src/helpers.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { toast } from "vue3-toastify";
|
||||
const { copy } = useClipboard();
|
||||
|
||||
export const copyLink = (url: string) => {
|
||||
copy(url);
|
||||
toast.success("Link Copied to Clipboard");
|
||||
};
|
||||
17
frontend/src/main.ts
Normal file
17
frontend/src/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createApp } from "vue";
|
||||
import "vuetify/styles";
|
||||
import { createVuetify } from "vuetify";
|
||||
import * as components from "vuetify/components";
|
||||
import * as directives from "vuetify/directives";
|
||||
import App from "./App.vue";
|
||||
import router from "./router/index";
|
||||
import createToast from "vue3-toastify";
|
||||
|
||||
const toast = createToast;
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives,
|
||||
});
|
||||
|
||||
createApp(App).use(router).use(toast).use(vuetify).mount("#app");
|
||||
52
frontend/src/router/index.ts
Normal file
52
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||
const Login = () => import("../components/Login.vue");
|
||||
const CreateFlist = () => import("../components/CreateFlist.vue");
|
||||
const Home = () => import("../components/Home.vue");
|
||||
const UserFlist = () => import("../components/UserFlist.vue");
|
||||
const PreviewFlist = () => import("../components/PreviewFlist.vue");
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/login",
|
||||
name: "login",
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: "/myflists",
|
||||
name: "myflists",
|
||||
component: UserFlist,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/create",
|
||||
name: "create",
|
||||
component: CreateFlist,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/flists/:username/:id",
|
||||
name: "previewflist",
|
||||
component: PreviewFlist,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: Home,
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
router.beforeEach((to, _, next) => {
|
||||
const token: string | null = sessionStorage.getItem("token");
|
||||
if (to.meta.requiresAuth && (token == null || token.length == 0)) {
|
||||
next({ name: "login" });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
21
frontend/src/style.css
Normal file
21
frontend/src/style.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.background-green {
|
||||
background-color: #1aa18f !important;
|
||||
}
|
||||
.thick-border .v-data-table__wrapper {
|
||||
border: 3px solid #000;
|
||||
}
|
||||
.v-data-table-footer__items-per-page {
|
||||
display: none !important;
|
||||
}
|
||||
.v-data-table td{
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.mn-height {
|
||||
min-height: calc(100% - 7%);
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
30
frontend/src/types/Flist.ts
Normal file
30
frontend/src/types/Flist.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export interface Flist {
|
||||
auth: string;
|
||||
email: string;
|
||||
identity_token: string;
|
||||
image_name: string;
|
||||
password: string;
|
||||
registry_token: string;
|
||||
server_address: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
|
||||
export interface FlistBody {
|
||||
is_file: Boolean;
|
||||
last_modified: bigint;
|
||||
name: string;
|
||||
path_uri: string;
|
||||
progress: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface FlistsResponseInterface {
|
||||
[key: string]: FlistBody[];
|
||||
}
|
||||
|
||||
export interface FlistPreview{
|
||||
checksum: string;
|
||||
content: string[];
|
||||
metadata: string;
|
||||
}
|
||||
4
frontend/src/types/User.ts
Normal file
4
frontend/src/types/User.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface User {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
1
frontend/src/vite-env.d.ts
vendored
Normal file
1
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
27
frontend/tsconfig.app.json
Normal file
27
frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2021",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2021", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
11
frontend/tsconfig.json
Normal file
11
frontend/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
frontend/tsconfig.node.json
Normal file
13
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
frontend/vite.config.ts
Normal file
7
frontend/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
||||
Reference in New Issue
Block a user