diff options
Diffstat (limited to 'src/webapp')
| -rw-r--r-- | src/webapp/src/components/carbon-extras/NativeDateInput.svelte | 21 | ||||
| -rw-r--r-- | src/webapp/src/lib/api/account.ts | 26 | ||||
| -rw-r--r-- | src/webapp/src/lib/configuration.ts | 6 | ||||
| -rw-r--r-- | src/webapp/src/lib/models/IAccessToken.ts | 9 | ||||
| -rw-r--r-- | src/webapp/src/lib/models/ICreateTokenRequest.ts | 7 | ||||
| -rw-r--r-- | src/webapp/src/routes/app/_header.svelte | 11 | ||||
| -rw-r--r-- | src/webapp/src/routes/app/modals/access-tokens-modal.svelte | 148 | ||||
| -rw-r--r-- | src/webapp/src/routes/app/modals/new-access-token-modal.svelte | 131 |
8 files changed, 356 insertions, 3 deletions
diff --git a/src/webapp/src/components/carbon-extras/NativeDateInput.svelte b/src/webapp/src/components/carbon-extras/NativeDateInput.svelte new file mode 100644 index 0000000..ccdec53 --- /dev/null +++ b/src/webapp/src/components/carbon-extras/NativeDateInput.svelte @@ -0,0 +1,21 @@ +<script lang="ts"> + import {generate_unsafe_id} from "@/lib/helpers"; + + export let value; + export let minDate; + export let label; + export let id = generate_unsafe_id(9); +</script> + +<div class="bx--date-picker-container"> + <label for="{id}" + class="bx--label">{label}</label> + <div class="bx--date-picker-input__wrapper"> + <input id="{id}" + type="date" + class="bx--date-picker__input" + placeholder="mm/dd/yyyy" + min={minDate} + bind:value> + </div> +</div> diff --git a/src/webapp/src/lib/api/account.ts b/src/webapp/src/lib/api/account.ts index f1c1708..eaf7592 100644 --- a/src/webapp/src/lib/api/account.ts +++ b/src/webapp/src/lib/api/account.ts @@ -1,5 +1,6 @@ import {api_base} from "@/lib/configuration"; import type {ICreateSessionRequest} from "@/lib/models/ICreateSessionRequest"; +import type {ICreateTokenRequest} from "@/lib/models/ICreateTokenRequest"; export async function create_session_async(request: ICreateSessionRequest): Promise<Response> { return fetch(api_base("account/create-session"), { @@ -25,3 +26,28 @@ export async function end_session_async(): Promise<Response> { credentials: "include" }); } + +export async function get_tokens_async(): Promise<Response> { + return fetch(api_base("account/tokens"), { + method: "get", + credentials: "include" + }); +} + +export async function create_token_async(request: ICreateTokenRequest): Promise<Response> { + return fetch(api_base("account/create-token"), { + method: "post", + credentials: "include", + body: JSON.stringify(request), + headers: { + "Content-Type": "application/json;charset=UTF-8" + } + }); +} + +export async function delete_token_async(token_id: string): Promise<Response> { + return fetch(api_base("account/delete-token?id=" + token_id), { + method: "delete", + credentials: "include" + }); +} diff --git a/src/webapp/src/lib/configuration.ts b/src/webapp/src/lib/configuration.ts index e7dbcfe..2b9c2af 100644 --- a/src/webapp/src/lib/configuration.ts +++ b/src/webapp/src/lib/configuration.ts @@ -1,7 +1,9 @@ -const api_version = "v1/"; +export const api_version = "v1"; +export const api_docs = "http://localhost:5003/swagger/index.html"; +export const api_url = "http://localhost:5003"; export function api_base(path) { - return is_development() ? "http://localhost:5003/" + api_version + path : "/" + api_version + path; + return `${api_url}/${api_version}/${path}`; } export function is_development() { diff --git a/src/webapp/src/lib/models/IAccessToken.ts b/src/webapp/src/lib/models/IAccessToken.ts new file mode 100644 index 0000000..218f888 --- /dev/null +++ b/src/webapp/src/lib/models/IAccessToken.ts @@ -0,0 +1,9 @@ +export interface IAccessToken { + id: string, + created: Date, + expiry_date: Date, + allow_read: boolean; + allow_create: boolean; + allow_update: boolean; + allow_delete: boolean; +} diff --git a/src/webapp/src/lib/models/ICreateTokenRequest.ts b/src/webapp/src/lib/models/ICreateTokenRequest.ts new file mode 100644 index 0000000..6e3c7a5 --- /dev/null +++ b/src/webapp/src/lib/models/ICreateTokenRequest.ts @@ -0,0 +1,7 @@ +export interface ICreateTokenRequest { + allow_read: boolean; + allow_create: boolean; + allow_update: boolean; + allow_delete: boolean; + expiry_date: Date; +} diff --git a/src/webapp/src/routes/app/_header.svelte b/src/webapp/src/routes/app/_header.svelte index 4b2ddb7..04eb405 100644 --- a/src/webapp/src/routes/app/_header.svelte +++ b/src/webapp/src/routes/app/_header.svelte @@ -12,11 +12,13 @@ import Help16 from "carbon-icons-svelte/lib/Help16"; import {end_session_async} from "@/lib/api/account"; import {clear_entries} from "@/lib/stores/entries"; + import AccessTokensModal from "./modals/access-tokens-modal.svelte"; let profile_dropdown_is_open = false; let options_dropdown_is_open = false; let enable_dark_theme = get(preferences).theme === ApplicationTheme.DARK; let enable_site_report = get(preferences).enable_site_report; + let access_token_modal_is_open = false; preferences.subscribe(e => { enable_site_report = e.enable_site_report; @@ -43,7 +45,11 @@ } function manage_access_tokens() { - alert("Not implemented"); + access_token_modal_is_open = true; + } + + function on_access_tokens_modal_close() { + access_token_modal_is_open = false; } function toggle_site_report() { @@ -62,6 +68,9 @@ } </script> +<AccessTokensModal open={access_token_modal_is_open} + on:close={on_access_tokens_modal_close}/> + <Header company="IOL" platformName="Bookmark Thing"> <HeaderUtilities> diff --git a/src/webapp/src/routes/app/modals/access-tokens-modal.svelte b/src/webapp/src/routes/app/modals/access-tokens-modal.svelte index e69de29..af4b524 100644 --- a/src/webapp/src/routes/app/modals/access-tokens-modal.svelte +++ b/src/webapp/src/routes/app/modals/access-tokens-modal.svelte @@ -0,0 +1,148 @@ +<script lang="ts"> + import {delete_token_async, get_tokens_async} from "@/lib/api/account"; + import {Modal, DataTable, ToolbarContent, Toolbar, ToolbarMenu, ToolbarMenuItem, Button, OverflowMenu, OverflowMenuItem, CodeSnippet, Link} from "carbon-components-svelte"; + import {onMount} from "svelte"; + import type {IAccessToken} from "@/lib/models/IAccessToken"; + import {api_docs} from "@/lib/configuration"; + import NewAccessTokenModal from "./new-access-token-modal.svelte"; + + export let open; + let loading_tokens_table; + let tokens = [] as Array<IAccessToken>; + let create_access_token_modal_is_open = false; + let copy_access_token_modal_is_open = false; + let new_access_token = ""; + const table_headers = [ + {key: "created", value: "Created"}, + {key: "expiry_date", value: "Expires"}, + {key: "allow_create", value: "Create"}, + {key: "allow_read", value: "Read"}, + {key: "allow_update", value: "Update"}, + {key: "allow_delete", value: "Delete"}, + {key: "overflow", empty: true}, + ]; + + async function load_tokens() { + loading_tokens_table = true; + const get_tokens_request = await get_tokens_async(); + loading_tokens_table = false; + if (get_tokens_request.ok) { + const response_json = await get_tokens_request.json(); + if (response_json?.length > 0) { + let temparr = []; + for (const token of response_json as Array<IAccessToken>) { + temparr.push({ + id: token.id, + expiry_date: new Date(token.expiry_date).toLocaleDateString(), + created: new Date(token.created).toLocaleDateString(), + allow_read: token.allow_read, + allow_create: token.allow_create, + allow_update: token.allow_update, + allow_delete: token.allow_delete, + }); + } + tokens = temparr; + } else { + tokens = []; + } + } else { + tokens = []; + } + } + + async function delete_token(event) { + const row = event.target.closest("tr"); + const id = row.id.substring(4, row.id.length); + if (confirm("Are you sure you want to delete this token?")) { + const http_request = await delete_token_async(id); + if (http_request.ok) { + await load_tokens(); + } + } + } + + async function delete_all_tokens() { + if (confirm("Are you sure you want to delete all tokens?")) { + for (const token of tokens) { + await delete_token_async(token.id); + } + await load_tokens(); + } + } + + async function handle_token_created(e) { + const token = e.detail; + console.log("Created token", token); + create_access_token_modal_is_open = false; + new_access_token = token; + copy_access_token_modal_is_open = true; + await load_tokens(); + } + + function copy_access_token_modal_closed() { + new_access_token = ""; + copy_access_token_modal_is_open = false; + } + + onMount(async () => { + await load_tokens(); + }); +</script> + +<Modal size="lg" + passiveModal="{true}" + {open} + preventCloseOnClickOutside + modalHeading="Manage access tokens" + on:open + on:close> + <DataTable size="compact" + sortable="{true}" + headers={table_headers} + style="padding-bottom: 50px" + rows={tokens}> + <Toolbar size="sm"> + <ToolbarContent> + <ToolbarMenu> + <ToolbarMenuItem> + <Link href="{api_docs}" + target="_blank">API documentation + </Link> + </ToolbarMenuItem> + <ToolbarMenuItem danger + on:click={delete_all_tokens}> + Delete all tokens + </ToolbarMenuItem> + </ToolbarMenu> + <Button on:click={() => create_access_token_modal_is_open = true}>Create token</Button> + </ToolbarContent> + </Toolbar> + <svelte:fragment slot="cell" + let:cell> + {#if cell.key === "overflow"} + <OverflowMenu flipped + size="sm"> + <OverflowMenuItem danger + on:click={delete_token} + text="Delete"/> + </OverflowMenu> + {:else} + {cell.value} + {/if} + </svelte:fragment> + </DataTable> +</Modal> + +<Modal size="sm" + passiveModal="{true}" + preventCloseOnClickOutside + modalHeading="Your access token" + modalDescription="This will only be shown once" + on:close={copy_access_token_modal_closed} + open={copy_access_token_modal_is_open}> + <CodeSnippet code={new_access_token}/> + <br> +</Modal> + +<NewAccessTokenModal bind:open={create_access_token_modal_is_open} + on:created={handle_token_created}/> diff --git a/src/webapp/src/routes/app/modals/new-access-token-modal.svelte b/src/webapp/src/routes/app/modals/new-access-token-modal.svelte new file mode 100644 index 0000000..8999b0b --- /dev/null +++ b/src/webapp/src/routes/app/modals/new-access-token-modal.svelte @@ -0,0 +1,131 @@ +<script lang="ts"> + import {Checkbox, Form, FormGroup, Modal} from "carbon-components-svelte"; + import NativeDateInput from "@/components/carbon-extras/NativeDateInput.svelte"; + import type {ICreateTokenRequest} from "@/lib/models/ICreateTokenRequest"; + import {create_token_async} from "@/lib/api/account"; + import {createEventDispatcher} from "svelte"; + + export let open = false; + const default_expires = (new Date().setTime(new Date().getTime() + 1)); + const dispatch = createEventDispatcher(); + const form = { + error: "", + loading: false, + expiry_date: { + value: default_expires, + error: "", + warning: "", + reset() { + form.expiry_date.value = default_expires; + }, + change() { + }, + validate() { + } + }, + allow_create: { + value: false, + error: "", + warning: "", + reset() { + form.allow_create.value = false; + }, + change() { + }, + validate() { + } + }, + allow_read: { + value: false, + error: "", + warning: "", + reset() { + form.allow_read.value = false; + }, + change() { + }, + validate() { + } + }, + allow_update: { + value: false, + error: "", + warning: "", + reset() { + form.allow_update.value = false; + }, + change() { + }, + validate() { + } + }, + allow_delete: { + value: false, + error: "", + warning: "", + reset() { + form.allow_delete.value = false; + }, + change() { + }, + validate() { + } + }, + reset() { + form.allow_read.reset(); + form.allow_delete.reset(); + form.allow_update.reset(); + form.allow_create.reset(); + form.expiry_date.reset(); + }, + payload(): ICreateTokenRequest { + return { + allow_delete: form.allow_delete.value, + allow_read: form.allow_read.value, + allow_update: form.allow_update.value, + allow_create: form.allow_create.value, + expiry_date: form.expiry_date.value, + } as ICreateTokenRequest; + }, + async submit() { + form.loading = true; + const http_request = await create_token_async(form.payload()); + form.loading = false; + if (http_request.ok) { + dispatch("created", await http_request.text()); + } + } + }; +</script> + +<Modal modalHeading="Create access token" + preventCloseOnClickOutside + hasForm + primaryButtonText="Submit" + secondaryButtonText="Cancel" + style="padding-bottom: 50px;padding-right: 30px;" + on:click:button--secondary={() => open = false} + bind:open={open} + on:submit={form.submit}> + <Form> + <FormGroup> + <NativeDateInput label="Expiry date" + minDate="{new Date()}" + bind:value={form.expiry_date.value}/> + </FormGroup> + <FormGroup legendText="Permissions"> + <Checkbox id="permission-create" + labelText="Create" + bind:checked="{form.allow_create.value}"/> + <Checkbox id="permission-read" + labelText="Read" + bind:checked="{form.allow_read.value}"/> + <Checkbox id="permission-update" + labelText="Update" + bind:checked="{form.allow_update.value}"/> + <Checkbox id="permission-delete" + labelText="Delete" + bind:checked="{form.allow_delete.value}"/> + </FormGroup> + </Form> +</Modal> |
