Add S3 password settings to edit window
This commit is contained in:
@@ -299,6 +299,12 @@ export const path_link = (node: HTMLAnchorElement, data: { nav: FSNavigator, nod
|
|||||||
|
|
||||||
// Override click handler
|
// Override click handler
|
||||||
const click = (e: MouseEvent) => {
|
const click = (e: MouseEvent) => {
|
||||||
|
// Modified clicks open the link in a new tab or window, let the browser
|
||||||
|
// handle those
|
||||||
|
if (e.button !== 0 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
data.nav.navigate(data.node.path, true)
|
data.nav.navigate(data.node.path, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from "layout/Button.svelte";
|
import Button from "layout/Button.svelte";
|
||||||
|
import ToggleButton from "layout/ToggleButton.svelte";
|
||||||
import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI.svelte";
|
import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI.svelte";
|
||||||
import PermissionButton from "./PermissionButton.svelte";
|
import PermissionButton from "./PermissionButton.svelte";
|
||||||
|
import { formatDate } from "util/Formatting";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
options = $bindable()
|
options = $bindable()
|
||||||
@@ -16,7 +18,7 @@ const add_user = (e: SubmitEvent) => {
|
|||||||
if (options.user_permissions === undefined) {
|
if (options.user_permissions === undefined) {
|
||||||
options.user_permissions = {} as {[index: string]: FSPermissions}
|
options.user_permissions = {} as {[index: string]: FSPermissions}
|
||||||
}
|
}
|
||||||
options.user_permissions[new_user_id] = structuredClone(new_user_perms)
|
options.user_permissions[new_user_id] = $state.snapshot(new_user_perms)
|
||||||
}
|
}
|
||||||
const del_user = (id: string) => {
|
const del_user = (id: string) => {
|
||||||
delete options.user_permissions[id]
|
delete options.user_permissions[id]
|
||||||
@@ -24,14 +26,26 @@ const del_user = (id: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let new_password = $state("")
|
let new_password = $state("")
|
||||||
|
let new_password_label = $state("")
|
||||||
let new_password_perms = $state(<FSPermissions>{read: true})
|
let new_password_perms = $state(<FSPermissions>{read: true})
|
||||||
const add_password = (e: SubmitEvent) => {
|
const add_password = (e: SubmitEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (options.password_permissions === undefined) {
|
if (options.password_permissions === undefined) {
|
||||||
options.password_permissions = {} as {[index: string]: FSPermissions}
|
options.password_permissions = {} as {[index: string]: FSPermissions}
|
||||||
}
|
}
|
||||||
options.password_permissions[new_password] = structuredClone(new_password_perms)
|
options.password_permissions[new_password] = {
|
||||||
|
...$state.snapshot(new_password_perms),
|
||||||
|
label: new_password_label,
|
||||||
|
}
|
||||||
|
new_password = ""
|
||||||
|
new_password_label = ""
|
||||||
}
|
}
|
||||||
|
const generate_password = () => {
|
||||||
|
// 32 hexadecimal characters, which any S3 client accepts as secret key
|
||||||
|
new_password = crypto.randomUUID().replaceAll("-", "")
|
||||||
|
}
|
||||||
|
// Passwords stay hidden until their reveal button is pressed
|
||||||
|
let revealed: {[password: string]: boolean} = $state({})
|
||||||
const del_password = (pass: string) => {
|
const del_password = (pass: string) => {
|
||||||
delete options.password_permissions[pass]
|
delete options.password_permissions[pass]
|
||||||
options.password_permissions = options.password_permissions
|
options.password_permissions = options.password_permissions
|
||||||
@@ -69,11 +83,14 @@ const del_password = (pass: string) => {
|
|||||||
</p>
|
</p>
|
||||||
<form onsubmit={add_user} class="row">
|
<form onsubmit={add_user} class="row">
|
||||||
<input type="text" bind:value={new_user_id} placeholder="Username" class="grow" size="1">
|
<input type="text" bind:value={new_user_id} placeholder="Username" class="grow" size="1">
|
||||||
<Button type="submit" icon="add" label="Add"/>
|
|
||||||
<div class="perms">
|
<div class="perms">
|
||||||
<PermissionButton bind:permissions={new_user_perms}/>
|
<PermissionButton bind:permissions={new_user_perms}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="perms">
|
||||||
|
<Button type="submit" icon="add" label="Add"/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<hr/>
|
||||||
{#if options.user_permissions !== undefined}
|
{#if options.user_permissions !== undefined}
|
||||||
{#each Object.keys(options.user_permissions) as id (id)}
|
{#each Object.keys(options.user_permissions) as id (id)}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -81,6 +98,13 @@ const del_password = (pass: string) => {
|
|||||||
<div class="grow id">
|
<div class="grow id">
|
||||||
{id}
|
{id}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="id">
|
||||||
|
{#if options.user_permissions[id].last_used}
|
||||||
|
Used {formatDate(options.user_permissions[id].last_used, true, true, false)}
|
||||||
|
{:else}
|
||||||
|
Never used
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<div class="perms">
|
<div class="perms">
|
||||||
<PermissionButton bind:permissions={options.user_permissions[id]}/>
|
<PermissionButton bind:permissions={options.user_permissions[id]}/>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,23 +117,35 @@ const del_password = (pass: string) => {
|
|||||||
<legend>Password permissions</legend>
|
<legend>Password permissions</legend>
|
||||||
<p>
|
<p>
|
||||||
Allow users to enter a password to give them access to this directory.
|
Allow users to enter a password to give them access to this directory.
|
||||||
</p>
|
Passwords can be used as S3 secret keys, see S3 access below.
|
||||||
<p>
|
|
||||||
<b>This feature is not implemented currently!</b>
|
|
||||||
</p>
|
</p>
|
||||||
<form onsubmit={add_password} class="row">
|
<form onsubmit={add_password} class="row">
|
||||||
<input type="text" bind:value={new_password} placeholder="Password" class="grow" size="1">
|
<input type="text" bind:value={new_password_label} placeholder="Label" class="grow" maxlength="100" size="1">
|
||||||
<Button type="submit" icon="add" label="Add"/>
|
<input type="text" bind:value={new_password} placeholder="Password" class="grow" size="1" minlength="8" maxlength="50">
|
||||||
|
<Button type="button" click={generate_password} icon="key" label="Generate"/>
|
||||||
<div class="perms">
|
<div class="perms">
|
||||||
<PermissionButton bind:permissions={new_password_perms}/>
|
<PermissionButton bind:permissions={new_password_perms}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="perms">
|
||||||
|
<Button type="submit" icon="add" label="Add"/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<hr/>
|
||||||
{#if options.password_permissions !== undefined}
|
{#if options.password_permissions !== undefined}
|
||||||
{#each Object.keys(options.password_permissions) as password (password)}
|
{#each Object.keys(options.password_permissions) as password (password)}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Button click={() => del_password(password)} icon="delete"/>
|
<Button click={() => del_password(password)} icon="delete"/>
|
||||||
|
<input type="text" bind:value={options.password_permissions[password].label} placeholder="Label" class="grow" maxlength="100" size="1">
|
||||||
|
<ToggleButton bind:on={revealed[password]} icon_on="visibility" icon_off="visibility_off"/>
|
||||||
<div class="grow id">
|
<div class="grow id">
|
||||||
{password}
|
{revealed[password] ? password : "••••••••••••••••"}
|
||||||
|
</div>
|
||||||
|
<div class="id">
|
||||||
|
{#if options.password_permissions[password].last_used}
|
||||||
|
Used {formatDate(options.password_permissions[password].last_used, true, true, false)}
|
||||||
|
{:else}
|
||||||
|
Never used
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="perms">
|
<div class="perms">
|
||||||
<PermissionButton bind:permissions={options.password_permissions[password]}/>
|
<PermissionButton bind:permissions={options.password_permissions[password]}/>
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ const save = async (e: SubmitEvent) => {
|
|||||||
<SharingOptions
|
<SharingOptions
|
||||||
bind:file
|
bind:file
|
||||||
bind:options
|
bind:options
|
||||||
|
owner={$nav.permissions.owner}
|
||||||
/>
|
/>
|
||||||
{:else if tab === "branding"}
|
{:else if tab === "branding"}
|
||||||
<BrandingOptions
|
<BrandingOptions
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import { user } from "lib/UserStore";
|
|||||||
let {
|
let {
|
||||||
file = $bindable(),
|
file = $bindable(),
|
||||||
options = $bindable(),
|
options = $bindable(),
|
||||||
|
owner = false,
|
||||||
}: {
|
}: {
|
||||||
file?: FSNode;
|
file?: FSNode;
|
||||||
options: NodeOptions;
|
options: NodeOptions;
|
||||||
|
owner?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let is_shared = $derived(file.is_shared() || options.link_permissions?.read)
|
let is_shared = $derived(file.is_shared() || options.link_permissions?.read)
|
||||||
@@ -42,6 +44,7 @@ const toggle_example = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d/"+file.id)
|
let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d/"+file.id)
|
||||||
|
const s3_endpoint = window.location.protocol+"//s3."+window.location.host
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $user.subscription.file_sharing === false}
|
{#if $user.subscription.file_sharing === false}
|
||||||
@@ -69,7 +72,26 @@ let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<AccessControl options={options}/>
|
<AccessControl bind:options={options}/>
|
||||||
|
|
||||||
|
{#if owner && file.type === "dir" && file.id !== "me"}
|
||||||
|
<fieldset>
|
||||||
|
<legend>S3 access</legend>
|
||||||
|
<p>
|
||||||
|
This directory can be used as a bucket by backup tools and other
|
||||||
|
software which supports Amazon S3. The directory ID is the access key,
|
||||||
|
and the passwords above are the secret keys. Each password's permissions
|
||||||
|
decide what it can do.
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Endpoint</td><td>{s3_endpoint}</td><td><CopyButton text={s3_endpoint}>Copy</CopyButton></td></tr>
|
||||||
|
<tr><td>Bucket and access key</td><td>{file.id}</td><td><CopyButton text={file.id}>Copy</CopyButton></td></tr>
|
||||||
|
<tr><td>Region</td><td>us-east-1 (any region works)</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Embedding</legend>
|
<legend>Embedding</legend>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ let {
|
|||||||
group_first = false,
|
group_first = false,
|
||||||
group_middle = false,
|
group_middle = false,
|
||||||
group_last = false,
|
group_last = false,
|
||||||
highlight = true,
|
highlight = false,
|
||||||
flat = false,
|
flat = false,
|
||||||
action,
|
action,
|
||||||
children,
|
children,
|
||||||
@@ -35,16 +35,16 @@ const click = (e: MouseEvent) => {
|
|||||||
onclick={click}
|
onclick={click}
|
||||||
type="button"
|
type="button"
|
||||||
class="button"
|
class="button"
|
||||||
class:button_highlight={on && highlight}
|
class:button_highlight={highlight}
|
||||||
class:group_first
|
class:group_first
|
||||||
class:group_middle
|
class:group_middle
|
||||||
class:group_last
|
class:group_last
|
||||||
class:flat
|
class:flat
|
||||||
>
|
>
|
||||||
{#if on}
|
{#if on}
|
||||||
<i class="icon">{icon_on}</i>
|
<i class="icon green">{icon_on}</i>
|
||||||
{:else}
|
{:else}
|
||||||
<i class="icon">{icon_off}</i>
|
<i class="icon red">{icon_off}</i>
|
||||||
{/if}
|
{/if}
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</button>
|
</button>
|
||||||
@@ -53,4 +53,10 @@ const click = (e: MouseEvent) => {
|
|||||||
.button {
|
.button {
|
||||||
flex: 0 0 content;
|
flex: 0 0 content;
|
||||||
}
|
}
|
||||||
|
.green {
|
||||||
|
color: var(--highlight_color);
|
||||||
|
}
|
||||||
|
.red {
|
||||||
|
color: var(--danger_color);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ export type FSPermissions = {
|
|||||||
read: boolean,
|
read: boolean,
|
||||||
write: boolean,
|
write: boolean,
|
||||||
delete: boolean,
|
delete: boolean,
|
||||||
|
|
||||||
|
// Only used for passwords
|
||||||
|
label?: string,
|
||||||
|
last_used?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FSContext = {
|
export type FSContext = {
|
||||||
@@ -289,16 +293,6 @@ export const fs_timeseries = async (path: string, start: Date, end: Date, interv
|
|||||||
) as NodeTimeSeries
|
) as NodeTimeSeries
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fs_import = async (parent_dir_path = "", filelist: Array<string>) => {
|
|
||||||
const form = new FormData()
|
|
||||||
form.append("action", "import")
|
|
||||||
form.append("files", JSON.stringify(filelist))
|
|
||||||
|
|
||||||
return await fs_check_response(
|
|
||||||
await fetch(fs_path_url(parent_dir_path), { method: "POST", body: form })
|
|
||||||
) as GenericResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
// =================
|
// =================
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user