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
|
||||
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()
|
||||
data.nav.navigate(data.node.path, true)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Button from "layout/Button.svelte";
|
||||
import ToggleButton from "layout/ToggleButton.svelte";
|
||||
import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI.svelte";
|
||||
import PermissionButton from "./PermissionButton.svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
|
||||
let {
|
||||
options = $bindable()
|
||||
@@ -16,7 +18,7 @@ const add_user = (e: SubmitEvent) => {
|
||||
if (options.user_permissions === undefined) {
|
||||
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) => {
|
||||
delete options.user_permissions[id]
|
||||
@@ -24,14 +26,26 @@ const del_user = (id: string) => {
|
||||
}
|
||||
|
||||
let new_password = $state("")
|
||||
let new_password_label = $state("")
|
||||
let new_password_perms = $state(<FSPermissions>{read: true})
|
||||
const add_password = (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
if (options.password_permissions === undefined) {
|
||||
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) => {
|
||||
delete options.password_permissions[pass]
|
||||
options.password_permissions = options.password_permissions
|
||||
@@ -69,11 +83,14 @@ const del_password = (pass: string) => {
|
||||
</p>
|
||||
<form onsubmit={add_user} class="row">
|
||||
<input type="text" bind:value={new_user_id} placeholder="Username" class="grow" size="1">
|
||||
<Button type="submit" icon="add" label="Add"/>
|
||||
<div class="perms">
|
||||
<PermissionButton bind:permissions={new_user_perms}/>
|
||||
</div>
|
||||
<div class="perms">
|
||||
<Button type="submit" icon="add" label="Add"/>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
{#if options.user_permissions !== undefined}
|
||||
{#each Object.keys(options.user_permissions) as id (id)}
|
||||
<div class="row">
|
||||
@@ -81,6 +98,13 @@ const del_password = (pass: string) => {
|
||||
<div class="grow id">
|
||||
{id}
|
||||
</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">
|
||||
<PermissionButton bind:permissions={options.user_permissions[id]}/>
|
||||
</div>
|
||||
@@ -93,23 +117,35 @@ const del_password = (pass: string) => {
|
||||
<legend>Password permissions</legend>
|
||||
<p>
|
||||
Allow users to enter a password to give them access to this directory.
|
||||
</p>
|
||||
<p>
|
||||
<b>This feature is not implemented currently!</b>
|
||||
Passwords can be used as S3 secret keys, see S3 access below.
|
||||
</p>
|
||||
<form onsubmit={add_password} class="row">
|
||||
<input type="text" bind:value={new_password} placeholder="Password" class="grow" size="1">
|
||||
<Button type="submit" icon="add" label="Add"/>
|
||||
<input type="text" bind:value={new_password_label} placeholder="Label" class="grow" maxlength="100" size="1">
|
||||
<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">
|
||||
<PermissionButton bind:permissions={new_password_perms}/>
|
||||
</div>
|
||||
<div class="perms">
|
||||
<Button type="submit" icon="add" label="Add"/>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
{#if options.password_permissions !== undefined}
|
||||
{#each Object.keys(options.password_permissions) as password (password)}
|
||||
<div class="row">
|
||||
<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">
|
||||
{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 class="perms">
|
||||
<PermissionButton bind:permissions={options.password_permissions[password]}/>
|
||||
|
||||
@@ -139,6 +139,7 @@ const save = async (e: SubmitEvent) => {
|
||||
<SharingOptions
|
||||
bind:file
|
||||
bind:options
|
||||
owner={$nav.permissions.owner}
|
||||
/>
|
||||
{:else if tab === "branding"}
|
||||
<BrandingOptions
|
||||
|
||||
@@ -9,9 +9,11 @@ import { user } from "lib/UserStore";
|
||||
let {
|
||||
file = $bindable(),
|
||||
options = $bindable(),
|
||||
owner = false,
|
||||
}: {
|
||||
file?: FSNode;
|
||||
options: NodeOptions;
|
||||
owner?: boolean;
|
||||
} = $props();
|
||||
|
||||
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)
|
||||
const s3_endpoint = window.location.protocol+"//s3."+window.location.host
|
||||
</script>
|
||||
|
||||
{#if $user.subscription.file_sharing === false}
|
||||
@@ -69,7 +72,26 @@ let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d
|
||||
</div>
|
||||
</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>
|
||||
<legend>Embedding</legend>
|
||||
|
||||
@@ -6,7 +6,7 @@ let {
|
||||
group_first = false,
|
||||
group_middle = false,
|
||||
group_last = false,
|
||||
highlight = true,
|
||||
highlight = false,
|
||||
flat = false,
|
||||
action,
|
||||
children,
|
||||
@@ -35,16 +35,16 @@ const click = (e: MouseEvent) => {
|
||||
onclick={click}
|
||||
type="button"
|
||||
class="button"
|
||||
class:button_highlight={on && highlight}
|
||||
class:button_highlight={highlight}
|
||||
class:group_first
|
||||
class:group_middle
|
||||
class:group_last
|
||||
class:flat
|
||||
>
|
||||
{#if on}
|
||||
<i class="icon">{icon_on}</i>
|
||||
<i class="icon green">{icon_on}</i>
|
||||
{:else}
|
||||
<i class="icon">{icon_off}</i>
|
||||
<i class="icon red">{icon_off}</i>
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</button>
|
||||
@@ -53,4 +53,10 @@ const click = (e: MouseEvent) => {
|
||||
.button {
|
||||
flex: 0 0 content;
|
||||
}
|
||||
.green {
|
||||
color: var(--highlight_color);
|
||||
}
|
||||
.red {
|
||||
color: var(--danger_color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -104,6 +104,10 @@ export type FSPermissions = {
|
||||
read: boolean,
|
||||
write: boolean,
|
||||
delete: boolean,
|
||||
|
||||
// Only used for passwords
|
||||
label?: string,
|
||||
last_used?: string,
|
||||
}
|
||||
|
||||
export type FSContext = {
|
||||
@@ -289,16 +293,6 @@ export const fs_timeseries = async (path: string, start: Date, end: Date, interv
|
||||
) 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
|
||||
// =================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user