Implemented routing instead of internal state
All checks were successful
/ Build the server (push) Successful in 3m5s
All checks were successful
/ Build the server (push) Successful in 3m5s
This commit is contained in:
parent
7334bd8e71
commit
c8b2ae30c8
20
frontend/package-lock.json
generated
20
frontend/package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"@microsoft/fetch-event-source": "^2.0.1",
|
"@microsoft/fetch-event-source": "^2.0.1",
|
||||||
"filesize": "^10.1.0",
|
"filesize": "^10.1.0",
|
||||||
"qrcode-svg": "^1.1.0",
|
"qrcode-svg": "^1.1.0",
|
||||||
|
"svelte-spa-router": "^3.3.0",
|
||||||
"tailwind-merge": "^1.14.0"
|
"tailwind-merge": "^1.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -2619,6 +2620,14 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regexparam": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRgSaYemnNYxUv+/5SeoHI0eJIgTL/A2pUtXUPLHQxUldagouJ9p+K6IbIZ/JiQuCEv2E2B1O11SjVQy3aMCkw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/relateurl": {
|
"node_modules/relateurl": {
|
||||||
"version": "0.2.7",
|
"version": "0.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
|
||||||
@ -3037,6 +3046,17 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/svelte-spa-router": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-cwRNe7cxD43sCvSfEeaKiNZg3FCizGxeMcf7CPiWRP3jKXjEma3vxyyuDtPOam6nWbVxl9TNM3hlE/i87ZlqcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"regexparam": "2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ItalyPaleAle"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/svg.draggable.js": {
|
"node_modules/svg.draggable.js": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"@microsoft/fetch-event-source": "^2.0.1",
|
"@microsoft/fetch-event-source": "^2.0.1",
|
||||||
"filesize": "^10.1.0",
|
"filesize": "^10.1.0",
|
||||||
"qrcode-svg": "^1.1.0",
|
"qrcode-svg": "^1.1.0",
|
||||||
|
"svelte-spa-router": "^3.3.0",
|
||||||
"tailwind-merge": "^1.14.0"
|
"tailwind-merge": "^1.14.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {changeStateFunction, error_banner, info_banner, rpc, session, show_working, state, StateE, token, workingWrapperO} from './store';
|
import {error_banner, info_banner, rpc, session, show_working, token, workingWrapperO} from './store';
|
||||||
import {Banner, Navbar, Spinner} from 'flowbite-svelte';
|
import {Banner, Navbar, NavBrand, Spinner} from 'flowbite-svelte';
|
||||||
|
import Router, {replace} from 'svelte-spa-router';
|
||||||
|
import {routes} from './routes';
|
||||||
import {FileStorage} from './icons';
|
import {FileStorage} from './icons';
|
||||||
import LinkButton from './components/LinkButton.svelte';
|
import LinkButton from './components/LinkButton.svelte';
|
||||||
import Login from './pages/Login.svelte';
|
import A from './components/A.svelte';
|
||||||
import Signup from './pages/Signup.svelte';
|
|
||||||
import ResetPassword from './pages/ResetPassword.svelte';
|
|
||||||
import Profile from './pages/Profile.svelte';
|
|
||||||
import TfaSetup from './pages/TfaSetup.svelte';
|
|
||||||
import Admin from './pages/Admin.svelte';
|
|
||||||
import View from './pages/View.svelte';
|
|
||||||
|
|
||||||
const s = session.s;
|
const s = session.s;
|
||||||
|
|
||||||
function homeClick() {
|
|
||||||
if ($token == null)
|
|
||||||
$state.s = StateE.LOGIN;
|
|
||||||
else
|
|
||||||
$state = { s: StateE.VIEW, view_node: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function leaveSudo() {
|
async function leaveSudo() {
|
||||||
await workingWrapperO(() => rpc.Admin_unsudo($token ?? ''));
|
await workingWrapperO(() => rpc.Admin_unsudo($token ?? ''));
|
||||||
await session.update($token);
|
await session.update($token);
|
||||||
state.set({s: StateE.ADMIN, view_node: 0});
|
await replace('/admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
rpc.Auth_logout($token ?? '');
|
rpc.Auth_logout($token ?? '');
|
||||||
token.set(null);
|
token.set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
homeClick();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="h-screen w-screen p-4 flex flex-col">
|
<main class="h-screen w-screen p-4 flex flex-col">
|
||||||
@ -49,43 +36,22 @@
|
|||||||
<Banner position="absolute" dismissable={false}><Spinner size="5" class="mr-2" />Working</Banner>
|
<Banner position="absolute" dismissable={false}><Spinner size="5" class="mr-2" />Working</Banner>
|
||||||
{/if}
|
{/if}
|
||||||
<Navbar class="flex-grow-0">
|
<Navbar class="flex-grow-0">
|
||||||
<button on:click={homeClick} id="home-button" class="flex items-center">
|
<NavBrand href={$token == null ? '#/login' : '#/view/0'}>
|
||||||
<FileStorage width="1.5em" height="1.5em"/>
|
<FileStorage width="1.5em" height="1.5em"/>
|
||||||
<span id="navbar-text">MFileserver</span>
|
<span id="navbar-text" class="ml-2">MFileserver</span>
|
||||||
</button>
|
</NavBrand>
|
||||||
|
|
||||||
{#if $token != null}
|
{#if $token != null}
|
||||||
<div class="flex md:order-2">
|
<div class="flex md:order-2 gap-x-2">
|
||||||
{#if $s?.sudo} <LinkButton on:click={leaveSudo}>Leave sudo</LinkButton> {/if}
|
{#if $s?.sudo} <LinkButton on:click={leaveSudo}>Leave sudo</LinkButton> {/if}
|
||||||
{#if $s?.admin} <LinkButton on:click={changeStateFunction(StateE.ADMIN)}>Admin</LinkButton> {/if}
|
{#if $s?.admin} <A href="#/admin">Admin</A> {/if}
|
||||||
<LinkButton on:click={changeStateFunction(StateE.VIEW, 0)}>Files</LinkButton>
|
<A href="#/view/0">Files</A>
|
||||||
<LinkButton on:click={changeStateFunction(StateE.PROFILE)}>Profile</LinkButton>
|
<A href="#/profile">Profile</A>
|
||||||
<LinkButton on:click={logout}>Logout</LinkButton>
|
<LinkButton on:click={logout}>Logout</LinkButton>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
<span class="grid justify-items-center mt-10">
|
<span class="grid justify-items-center mt-10">
|
||||||
{#if $state.s === StateE.LOGIN } <Login/>
|
<Router {routes} />
|
||||||
{:else if $state.s === StateE.SIGNUP} <Signup/>
|
|
||||||
{:else if $state.s === StateE.RESET_PASSWORD} <ResetPassword/>
|
|
||||||
{:else if $state.s === StateE.PROFILE} <Profile/>
|
|
||||||
{:else if $state.s === StateE.TFA_SETUP} <TfaSetup/>
|
|
||||||
{:else if $state.s === StateE.ADMIN} <Admin/>
|
|
||||||
{:else if $state.s === StateE.VIEW} <View/>
|
|
||||||
{:else} <span>You are in state {$state.s}, which should not be possible, please report this.</span>
|
|
||||||
{/if}
|
|
||||||
</span>
|
</span>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
|
||||||
#navbar-text {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
#home-button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
8
frontend/src/components/A.svelte
Normal file
8
frontend/src/components/A.svelte
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {A} from 'flowbite-svelte';
|
||||||
|
export let href: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<A {href} aClass="hover:text-primary-400 transition-colors">
|
||||||
|
<slot></slot>
|
||||||
|
</A>
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {rpc, show_working, state, token} from '../store';
|
import {rpc, show_working, token} from '../store';
|
||||||
import {Button, ButtonGroup, Modal} from 'flowbite-svelte';
|
import {Button, ButtonGroup, Modal} from 'flowbite-svelte';
|
||||||
import {afterUpdate} from 'svelte';
|
import {afterUpdate, createEventDispatcher} from 'svelte';
|
||||||
|
|
||||||
let show_confirm = false;
|
let show_confirm = false;
|
||||||
let show_modal = false;
|
let show_modal = false;
|
||||||
@ -9,6 +9,8 @@
|
|||||||
let text = '';
|
let text = '';
|
||||||
let nodes: number[] = [];
|
let nodes: number[] = [];
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{reload_node: null}>();
|
||||||
|
|
||||||
async function real_delete() {
|
async function real_delete() {
|
||||||
show_confirm = false;
|
show_confirm = false;
|
||||||
show_modal = true;
|
show_modal = true;
|
||||||
@ -27,7 +29,7 @@
|
|||||||
|
|
||||||
show_working.set(false);
|
show_working.set(false);
|
||||||
show_modal = false;
|
show_modal = false;
|
||||||
state.update(v => v);
|
dispatch('reload_node');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const del = async (n: number[]) => {
|
export const del = async (n: number[]) => {
|
||||||
|
@ -4,11 +4,12 @@
|
|||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Checkbox, Dropdown, DropdownItem, Spinner, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Tooltip} from 'flowbite-svelte';
|
import {Checkbox, Dropdown, DropdownItem, Spinner, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Tooltip} from 'flowbite-svelte';
|
||||||
import {Folder, FolderParent, DocumentBlank, CaretLeft} from '../icons';
|
|
||||||
import {filesize} from 'filesize';
|
import {filesize} from 'filesize';
|
||||||
import {api, changeStateFunction, download, StateE, token, rpc} from '../store';
|
import {Folder, FolderParent, DocumentBlank, CaretLeft} from '../icons';
|
||||||
|
import {api, download, token, rpc} from '../store';
|
||||||
import LinkButton from './LinkButton.svelte';
|
import LinkButton from './LinkButton.svelte';
|
||||||
import DeleteModal from './DeleteModal.svelte';
|
import DeleteModal from './DeleteModal.svelte';
|
||||||
|
import A from './A.svelte';
|
||||||
|
|
||||||
export let node: api.Node;
|
export let node: api.Node;
|
||||||
|
|
||||||
@ -43,11 +44,12 @@
|
|||||||
$: ctx_style = `top: ${ctx_y}px; left: ${ctx_x}px; position: fixed;`;
|
$: ctx_style = `top: ${ctx_y}px; left: ${ctx_x}px; position: fixed;`;
|
||||||
|
|
||||||
function onCtxMenu(node: api.Node, e: MouseEvent) {
|
function onCtxMenu(node: api.Node, e: MouseEvent) {
|
||||||
|
console.log(e);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!ctx_hidden)
|
if (!ctx_hidden)
|
||||||
return ctx_hidden = true;
|
return ctx_hidden = true;
|
||||||
ctx_x = e.pageX;
|
ctx_x = e.clientX;
|
||||||
ctx_y = e.pageY;
|
ctx_y = e.clientY;
|
||||||
ctx_node = node;
|
ctx_node = node;
|
||||||
ctx_hidden = false;
|
ctx_hidden = false;
|
||||||
}
|
}
|
||||||
@ -70,7 +72,7 @@
|
|||||||
|
|
||||||
<svelte:body on:click={() => (ctx_hidden = true)} />
|
<svelte:body on:click={() => (ctx_hidden = true)} />
|
||||||
|
|
||||||
<DeleteModal bind:del={del} />
|
<DeleteModal bind:del={del} on:reload_node />
|
||||||
|
|
||||||
<Table hoverable>
|
<Table hoverable>
|
||||||
<TableHead theadClass="text-xs">
|
<TableHead theadClass="text-xs">
|
||||||
@ -86,7 +88,7 @@
|
|||||||
<TableBodyRow>
|
<TableBodyRow>
|
||||||
<TableBodyCell class="!p-4"></TableBodyCell>
|
<TableBodyCell class="!p-4"></TableBodyCell>
|
||||||
<TableBodyCell class="px-2 w-0"><FolderParent /></TableBodyCell>
|
<TableBodyCell class="px-2 w-0"><FolderParent /></TableBodyCell>
|
||||||
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.parent ?? 0)}>..</LinkButton></TableBodyCell>
|
<TableBodyCell class="pl-0"><A href={'#/view/' + node.parent}>..</A></TableBodyCell>
|
||||||
<TableBodyCell></TableBodyCell>
|
<TableBodyCell></TableBodyCell>
|
||||||
</TableBodyRow>
|
</TableBodyRow>
|
||||||
{/if}
|
{/if}
|
||||||
@ -94,7 +96,7 @@
|
|||||||
<TableBodyRow on:contextmenu={onCtxMenu.bind(null, node)}>
|
<TableBodyRow on:contextmenu={onCtxMenu.bind(null, node)}>
|
||||||
<TableBodyCell class="p-2 pl-4 w-0 h-0"><Checkbox bind:group={selected} value={node.id}/></TableBodyCell>
|
<TableBodyCell class="p-2 pl-4 w-0 h-0"><Checkbox bind:group={selected} value={node.id}/></TableBodyCell>
|
||||||
<TableBodyCell class="px-2 w-0"><Folder /></TableBodyCell>
|
<TableBodyCell class="px-2 w-0"><Folder /></TableBodyCell>
|
||||||
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.id)}>{node.name}</LinkButton></TableBodyCell>
|
<TableBodyCell class="pl-0"><A href={'#/view/' + node.id}>{node.name}</A></TableBodyCell>
|
||||||
<TableBodyCell></TableBodyCell>
|
<TableBodyCell></TableBodyCell>
|
||||||
</TableBodyRow>
|
</TableBodyRow>
|
||||||
{/each}
|
{/each}
|
||||||
@ -112,7 +114,7 @@
|
|||||||
<DocumentBlank />
|
<DocumentBlank />
|
||||||
{/if}
|
{/if}
|
||||||
</TableBodyCell>
|
</TableBodyCell>
|
||||||
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.id)}>{node.name}</LinkButton></TableBodyCell>
|
<TableBodyCell class="pl-0"><A href={'#/view/' + node.id}>{node.name}</A></TableBodyCell>
|
||||||
<TableBodyCell>{filesize(node.size ?? 0, {base: 2, standard: 'jedec'})}</TableBodyCell>
|
<TableBodyCell>{filesize(node.size ?? 0, {base: 2, standard: 'jedec'})}</TableBodyCell>
|
||||||
</TableBodyRow>
|
</TableBodyRow>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
.link-button {
|
.link-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0 0.25em;
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {state, token, type UploadFile} from '../store';
|
import {token, type UploadFile} from '../store';
|
||||||
import {Button, Modal, Progressbar} from 'flowbite-svelte';
|
import {Button, Modal, Progressbar} from 'flowbite-svelte';
|
||||||
import {filesize} from 'filesize';
|
import {filesize} from 'filesize';
|
||||||
|
import {createEventDispatcher} from 'svelte';
|
||||||
|
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{reload_node: null}>();
|
||||||
|
|
||||||
interface MyFile extends UploadFile {
|
interface MyFile extends UploadFile {
|
||||||
waiting: boolean,
|
waiting: boolean,
|
||||||
@ -24,7 +28,7 @@
|
|||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
show_modal = false;
|
show_modal = false;
|
||||||
state.update(v => v);
|
dispatch('reload_node');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function realUpload(file: MyFile) {
|
async function realUpload(file: MyFile) {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import "./app.pcss";
|
import "./app.pcss";
|
||||||
import App from "./App.svelte";
|
import App from "./App.svelte";
|
||||||
import {state, StateE, token} from './store';
|
import {token} from './store';
|
||||||
|
import {replace} from 'svelte-spa-router';
|
||||||
|
|
||||||
token.subscribe(v => {
|
token.subscribe(v => {
|
||||||
if (v == null) state.set({s: StateE.LOGIN, view_node: 0});
|
if (v == null) replace('/login').then()
|
||||||
});
|
});
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {api, rpc, session, state, StateE, token, workingWrapperO, workingWrapperR} from '../store';
|
import {api, rpc, session, token, workingWrapperO, workingWrapperR} from '../store';
|
||||||
import {Checkbox, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell} from 'flowbite-svelte';
|
import {Checkbox, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell} from 'flowbite-svelte';
|
||||||
import {Checkmark, Error} from '../icons';
|
import {Checkmark, Error} from '../icons';
|
||||||
import LinkButton from '../components/LinkButton.svelte';
|
import LinkButton from '../components/LinkButton.svelte';
|
||||||
|
import {replace} from 'svelte-spa-router';
|
||||||
|
|
||||||
let users: api.UserInfo[] = [];
|
let users: api.UserInfo[] = [];
|
||||||
|
|
||||||
@ -25,7 +26,7 @@
|
|||||||
async function sudo(user: number) {
|
async function sudo(user: number) {
|
||||||
if (await workingWrapperO(() => rpc.Admin_sudo($token ?? '', user))) {
|
if (await workingWrapperO(() => rpc.Admin_sudo($token ?? '', user))) {
|
||||||
await session.update($token);
|
await session.update($token);
|
||||||
state.set({s: StateE.VIEW, view_node: 0});
|
await replace('/view/0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
||||||
import {Email, OTP, Password} from '../icons';
|
import {Email, OTP, Password} from '../icons';
|
||||||
import {changeStateFunction, rpc, state, StateE, token, workingWrapperR, api} from '../store';
|
import {rpc, token, workingWrapperR, api} from '../store';
|
||||||
|
import {replace} from 'svelte-spa-router';
|
||||||
|
|
||||||
let ask_tfa = false;
|
let ask_tfa = false;
|
||||||
let username = '', password = '', tfa = '';
|
let username = '', password = '', tfa = '';
|
||||||
@ -14,7 +15,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
token.set(resp.token);
|
token.set(resp.token);
|
||||||
state.set({s: StateE.VIEW, view_node: 0});
|
await replace('/view/0');
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyUp(e: KeyboardEvent) {
|
function keyUp(e: KeyboardEvent) {
|
||||||
@ -41,9 +42,9 @@
|
|||||||
<Input type="password" placeholder="Password" bind:value={password} on:keyup={keyUp}></Input>
|
<Input type="password" placeholder="Password" bind:value={password} on:keyup={keyUp}></Input>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup class="w-full flex flex-nowrap">
|
<ButtonGroup class="w-full flex flex-nowrap">
|
||||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.SIGNUP)}>Signup</Button>
|
<Button class="flex-1 flex-grow" color="primary" outline href="#/signup">Signup</Button>
|
||||||
<Button class="flex-1 flex-grow" color="primary" on:click={login}>Login</Button>
|
<Button class="flex-1 flex-grow" color="primary" on:click={login}>Login</Button>
|
||||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.RESET_PASSWORD)}>Forget password</Button>
|
<Button class="flex-1 flex-grow" color="primary" outline href="#/reset_pw">Forget password</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{/if}
|
{/if}
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {changeStateFunction, error_banner, rpc, session, StateE, token, workingWrapperO} from '../store';
|
import {error_banner, rpc, session, token, workingWrapperO} from '../store';
|
||||||
import {Accordion, AccordionItem, Button, ButtonGroup, Input, InputAddon} from 'flowbite-svelte';
|
import {Accordion, AccordionItem, Button, ButtonGroup, Input, InputAddon} from 'flowbite-svelte';
|
||||||
import {Password} from '../icons';
|
import {Password} from '../icons';
|
||||||
import {info_banner} from '../store.js';
|
import {info_banner} from '../store.js';
|
||||||
@ -69,7 +69,7 @@
|
|||||||
{#if tfa_enabled}
|
{#if tfa_enabled}
|
||||||
<Button class="w-full" color="red" on:click={disableTfa}>Disable</Button>
|
<Button class="w-full" color="red" on:click={disableTfa}>Disable</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<Button class="w-full" color="green" on:click={changeStateFunction(StateE.TFA_SETUP)}>Enable</Button>
|
<Button class="w-full" color="green" href="#/tfa">Enable</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
||||||
import {Email, EmailNew, Password} from '../icons';
|
import {Email, EmailNew, Password} from '../icons';
|
||||||
import {changeStateFunction, error_banner, info_banner, rpc, state, StateE, workingWrapper, workingWrapperO} from '../store';
|
import {error_banner, info_banner, rpc, workingWrapper, workingWrapperO} from '../store';
|
||||||
|
import {replace} from 'svelte-spa-router';
|
||||||
|
|
||||||
let enter_key = false;
|
let enter_key = false;
|
||||||
let username = '', key = '', password = '', password2 = '';
|
let username = '', key = '', password = '', password2 = '';
|
||||||
@ -20,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (await workingWrapperO(() => rpc.Auth_reset_password(key, password)))
|
if (await workingWrapperO(() => rpc.Auth_reset_password(key, password)))
|
||||||
$state.s = StateE.LOGIN;
|
await replace('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyUp(e: KeyboardEvent) {
|
function keyUp(e: KeyboardEvent) {
|
||||||
@ -49,7 +50,7 @@
|
|||||||
<Input type="password" placeholder="Repeat password" bind:value={password2} on:keyup={keyUp}></Input>
|
<Input type="password" placeholder="Repeat password" bind:value={password2} on:keyup={keyUp}></Input>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup class="w-full flex flex-nowrap">
|
<ButtonGroup class="w-full flex flex-nowrap">
|
||||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.LOGIN)}>Login</Button>
|
<Button class="flex-1 flex-grow" color="primary" outline href="#/login">Login</Button>
|
||||||
<Button class="flex-1 flex-grow" color="primary" on:click={changePw}>Change password</Button>
|
<Button class="flex-1 flex-grow" color="primary" on:click={changePw}>Change password</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{:else}
|
{:else}
|
||||||
@ -58,7 +59,7 @@
|
|||||||
<Input type="email" placeholder="Email" bind:value={username} on:keyup={keyUp}></Input>
|
<Input type="email" placeholder="Email" bind:value={username} on:keyup={keyUp}></Input>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup class="w-full flex flex-nowrap">
|
<ButtonGroup class="w-full flex flex-nowrap">
|
||||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.LOGIN)}>Login</Button>
|
<Button class="flex-1 flex-grow" color="primary" outline href="#/login">Login</Button>
|
||||||
<Button class="flex-1 flex-grow" color="primary" on:click={sendKey}>Send recovery key</Button>
|
<Button class="flex-1 flex-grow" color="primary" on:click={sendKey}>Send recovery key</Button>
|
||||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={() => (enter_key = true)}>Enter key</Button>
|
<Button class="flex-1 flex-grow" color="primary" outline on:click={() => (enter_key = true)}>Enter key</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
||||||
import {Email, Password} from '../icons';
|
import {Email, Password} from '../icons';
|
||||||
import {changeStateFunction, error_banner, info_banner, rpc, state, StateE, workingWrapperO} from '../store';
|
import {error_banner, info_banner, rpc, workingWrapperO} from '../store';
|
||||||
|
import {replace} from 'svelte-spa-router';
|
||||||
|
|
||||||
let username = '', username2 = '', password = '', password2 = '';
|
let username = '', username2 = '', password = '', password2 = '';
|
||||||
|
|
||||||
@ -20,7 +21,7 @@
|
|||||||
|
|
||||||
if (resp) {
|
if (resp) {
|
||||||
info_banner.set('Account created, please wait till an administrator approves it');
|
info_banner.set('Account created, please wait till an administrator approves it');
|
||||||
$state.s = StateE.LOGIN;
|
await replace('/login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +49,7 @@
|
|||||||
<Input type="password" placeholder="Repeat password" bind:value={password2} on:keyup={keyUp}></Input>
|
<Input type="password" placeholder="Repeat password" bind:value={password2} on:keyup={keyUp}></Input>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup class="w-full flex flex-nowrap">
|
<ButtonGroup class="w-full flex flex-nowrap">
|
||||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.LOGIN)}>Login</Button>
|
<Button class="flex-1 flex-grow" color="primary" outline href="#/login">Login</Button>
|
||||||
<Button class="flex-1 flex-grow" color="primary" on:click={signup}>Singup</Button>
|
<Button class="flex-1 flex-grow" color="primary" on:click={signup}>Singup</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Button, ButtonGroup, Card, Input, InputAddon, StepIndicator, Tooltip} from 'flowbite-svelte';
|
import {Button, ButtonGroup, Card, Input, InputAddon, StepIndicator, Tooltip} from 'flowbite-svelte';
|
||||||
import {OTP} from '../icons';
|
import {OTP} from '../icons';
|
||||||
import {info_banner, rpc, session, state, StateE, token, workingWrapperO, workingWrapperR} from '../store';
|
import {info_banner, rpc, session, token, workingWrapperO, workingWrapperR} from '../store';
|
||||||
import QRCode from 'qrcode-svg';
|
import QRCode from 'qrcode-svg';
|
||||||
|
import {replace} from 'svelte-spa-router';
|
||||||
|
|
||||||
const s = session.s;
|
const s = session.s;
|
||||||
|
|
||||||
@ -35,7 +36,6 @@
|
|||||||
async function completeSetup() {
|
async function completeSetup() {
|
||||||
if (await workingWrapperO(() => rpc.Auth_tfa_complete($token ?? '', code))) {
|
if (await workingWrapperO(() => rpc.Auth_tfa_complete($token ?? '', code))) {
|
||||||
info_banner.set("Successfully set up two factor authentication");
|
info_banner.set("Successfully set up two factor authentication");
|
||||||
$state.s = StateE.LOGIN;
|
|
||||||
token.set(null);
|
token.set(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,38 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Breadcrumb, Dropzone, Modal, Progressbar} from 'flowbite-svelte';
|
import {Breadcrumb, Dropzone, Modal, Progressbar} from 'flowbite-svelte';
|
||||||
import {derived} from 'svelte/store';
|
import {writable} from 'svelte/store';
|
||||||
import {CloudUpload} from '../icons';
|
import {CloudUpload} from '../icons';
|
||||||
import {api, changeStateFunction, rpc, state, StateE, token, type UploadFile, workingWrapperR} from '../store';
|
import {api, rpc, token, type UploadFile, workingWrapperR} from '../store';
|
||||||
import LinkButton from '../components/LinkButton.svelte';
|
|
||||||
import DirViewer from '../components/DirViewer.svelte';
|
import DirViewer from '../components/DirViewer.svelte';
|
||||||
import UploadModal from '../components/UploadModal.svelte';
|
import UploadModal from '../components/UploadModal.svelte';
|
||||||
import FileViewer from '../components/FileViewer.svelte';
|
import FileViewer from '../components/FileViewer.svelte';
|
||||||
|
import A from '../components/A.svelte';
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
node: api.Node|null,
|
node: api.Node|null,
|
||||||
segments: api.PathSegment[]
|
segments: api.PathSegment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = derived<typeof state, Data>(
|
export let params: {id?: string}|undefined = {};
|
||||||
state,
|
$: {
|
||||||
($state, set) => {
|
let id = 0;
|
||||||
(async () => {
|
if (params && params.id) {
|
||||||
let node = await workingWrapperR<api.Node>(() => rpc.FS_get_node($token ?? '', $state.view_node));
|
id = parseInt(params.id);
|
||||||
if (!node)
|
if (id >= 0)
|
||||||
return $state.view_node = 0;
|
updateData(id);
|
||||||
let segments = await workingWrapperR<api.PathSegment[]>(() => rpc.FS_get_path($token ?? '', node!.id));
|
}
|
||||||
if (!segments)
|
}
|
||||||
return $state.view_node = 0;
|
|
||||||
set({node: node as Data['node'], segments });
|
const data = writable<Data>({node: null, segments: []});
|
||||||
})();
|
async function updateData(id: number) {
|
||||||
},
|
let node = await workingWrapperR<api.Node>(() => rpc.FS_get_node($token ?? '', id));
|
||||||
{ node: null, segments: [] }
|
if (!node)
|
||||||
);
|
return;
|
||||||
|
let segments = await workingWrapperR<api.PathSegment[]>(() => rpc.FS_get_path($token ?? '', id));
|
||||||
|
if (!segments)
|
||||||
|
return;
|
||||||
|
data.set({node: node as Data['node'], segments });
|
||||||
|
}
|
||||||
|
|
||||||
const getFile = async (entry: FileSystemEntry) => new Promise<File>((o, e) => (entry as FileSystemFileEntry).file(o, e));
|
const getFile = async (entry: FileSystemEntry) => new Promise<File>((o, e) => (entry as FileSystemFileEntry).file(o, e));
|
||||||
|
|
||||||
@ -70,7 +75,7 @@
|
|||||||
const files: UploadFile[] = [];
|
const files: UploadFile[] = [];
|
||||||
for (const f of input.files)
|
for (const f of input.files)
|
||||||
files.push({
|
files.push({
|
||||||
id: $state.view_node,
|
id: $data.node?.id ?? 0,
|
||||||
name: f.name,
|
name: f.name,
|
||||||
full_name: f.name,
|
full_name: f.name,
|
||||||
file: f,
|
file: f,
|
||||||
@ -89,7 +94,7 @@
|
|||||||
if (!entry)
|
if (!entry)
|
||||||
console.error("Failed to get entry for: ", i);
|
console.error("Failed to get entry for: ", i);
|
||||||
else
|
else
|
||||||
files.push(...await handleEntry($state.view_node, '', entry));
|
files.push(...await handleEntry($data.node?.id ?? 0, '', entry));
|
||||||
}
|
}
|
||||||
await upload(files);
|
await upload(files);
|
||||||
}
|
}
|
||||||
@ -120,7 +125,7 @@
|
|||||||
{#if i > 0}<li class="inline-flex items-center">/</li>{/if}
|
{#if i > 0}<li class="inline-flex items-center">/</li>{/if}
|
||||||
<li class="inline-flex items-center">
|
<li class="inline-flex items-center">
|
||||||
{#if segment.id !== null}
|
{#if segment.id !== null}
|
||||||
<LinkButton on:click={changeStateFunction(StateE.VIEW, segment.id)}>{segment.id === 0 ? 'Files' : segment.name}</LinkButton>
|
<A href={'#/view/' + segment.id}>{segment.id === 0 ? 'Files' : segment.name}</A>
|
||||||
{:else}
|
{:else}
|
||||||
<span style="padding: 0 0.25em;">{segment.name}</span>
|
<span style="padding: 0 0.25em;">{segment.name}</span>
|
||||||
{/if}
|
{/if}
|
||||||
@ -142,10 +147,10 @@
|
|||||||
{:else if $data.node.file}
|
{:else if $data.node.file}
|
||||||
<FileViewer node={$data.node} />
|
<FileViewer node={$data.node} />
|
||||||
{:else}
|
{:else}
|
||||||
<DirViewer node={$data.node} />
|
<DirViewer node={$data.node} on:reload_node={() => updateData($data.node?.id ?? 0)} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<UploadModal bind:upload={real_upload}/>
|
<UploadModal bind:upload={real_upload} on:reload_node={() => updateData($data.node?.id ?? 0)} />
|
||||||
{#if upload_progress_data.current !== upload_progress_data.total}
|
{#if upload_progress_data.current !== upload_progress_data.total}
|
||||||
<Modal open dismissable={false} title="Creating files">
|
<Modal open dismissable={false} title="Creating files">
|
||||||
<div class="mb-1 flex justify-between">
|
<div class="mb-1 flex justify-between">
|
||||||
|
17
frontend/src/routes.ts
Normal file
17
frontend/src/routes.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Login from './pages/Login.svelte';
|
||||||
|
import Signup from './pages/Signup.svelte';
|
||||||
|
import ResetPassword from './pages/ResetPassword.svelte';
|
||||||
|
import Profile from './pages/Profile.svelte';
|
||||||
|
import TfaSetup from './pages/TfaSetup.svelte';
|
||||||
|
import Admin from './pages/Admin.svelte';
|
||||||
|
import View from './pages/View.svelte';
|
||||||
|
|
||||||
|
export const routes = {
|
||||||
|
'/login': Login,
|
||||||
|
'/signup': Signup,
|
||||||
|
'/reset_pw': ResetPassword,
|
||||||
|
'/profile': Profile,
|
||||||
|
'/tfa': TfaSetup,
|
||||||
|
'/admin': Admin,
|
||||||
|
'/view/:id': View
|
||||||
|
}
|
@ -1,14 +1,9 @@
|
|||||||
import {MRPCConnector, type Session, type Response} from './api';
|
import {MRPCConnector, type Session, type Response} from './api';
|
||||||
import {get, type Writable, writable} from 'svelte/store';
|
import {type Writable, writable} from 'svelte/store';
|
||||||
import {filesize} from 'filesize';
|
import {filesize} from 'filesize';
|
||||||
|
|
||||||
export * as api from './api';
|
export * as api from './api';
|
||||||
|
|
||||||
export enum StateE { LOGIN, SIGNUP, RESET_PASSWORD, PROFILE, TFA_SETUP, ADMIN, VIEW }
|
|
||||||
export interface State {
|
|
||||||
s: StateE,
|
|
||||||
view_node: number
|
|
||||||
}
|
|
||||||
export interface UploadFile {
|
export interface UploadFile {
|
||||||
id: number,
|
id: number,
|
||||||
name: string,
|
name: string,
|
||||||
@ -21,15 +16,12 @@ export const show_working = writable<boolean>(false);
|
|||||||
export const info_banner = writable<string>('');
|
export const info_banner = writable<string>('');
|
||||||
export const error_banner = writable<string>('');
|
export const error_banner = writable<string>('');
|
||||||
|
|
||||||
export const state = writable<State>({s: StateE.LOGIN, view_node: 0});
|
|
||||||
|
|
||||||
export const rpc = new MRPCConnector('/mrpc');
|
export const rpc = new MRPCConnector('/mrpc');
|
||||||
|
|
||||||
export const token = writable<string|null>(localStorage.getItem('token'));
|
export const token = writable<string|null>(localStorage.getItem('token'));
|
||||||
export const session: { s: Writable<Session|null>, update: (token: string|null) => Promise<void> } = {
|
export const session: { s: Writable<Session|null>, update: (token: string|null) => Promise<void> } = {
|
||||||
s: writable(null),
|
s: writable(null),
|
||||||
update: async (t: string|null) => {
|
update: async (t: string|null) => {
|
||||||
console.log('S');
|
|
||||||
if (t == null) {
|
if (t == null) {
|
||||||
session.s.set(null);
|
session.s.set(null);
|
||||||
return;
|
return;
|
||||||
@ -50,14 +42,6 @@ token.subscribe(v => {
|
|||||||
localStorage.setItem('token', v);
|
localStorage.setItem('token', v);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
export function changeStateFunction(target: StateE, node?: number): () => void {
|
|
||||||
return () => {
|
|
||||||
const new_node = node ?? get(state).view_node;
|
|
||||||
state.set({s: target, view_node: new_node});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function workingWrapper<T>(fn: () => Promise<T>): Promise<T|null> {
|
export async function workingWrapper<T>(fn: () => Promise<T>): Promise<T|null> {
|
||||||
let r = null;
|
let r = null;
|
||||||
error_banner.set('');
|
error_banner.set('');
|
||||||
|
Loading…
Reference in New Issue
Block a user