feat: alpha hub integration + frontend user store fixes + script client base_url fix
This commit is contained in:
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
2
backend/migrations/20220610181005_add_script_hub.up.sql
Normal file
2
backend/migrations/20220610181005_add_script_hub.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE JOB_KIND ADD VALUE 'script_hub';
|
||||
@@ -1210,6 +1210,52 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
/scripts/hub/list:
|
||||
get:
|
||||
summary: list all available hub scripts
|
||||
operationId: listHubScripts
|
||||
tags:
|
||||
- script
|
||||
responses:
|
||||
"200":
|
||||
description: hub scripts list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
summary:
|
||||
type: string
|
||||
app:
|
||||
type: string
|
||||
approved:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- summary
|
||||
- app
|
||||
- approved
|
||||
|
||||
/scripts/hub/get/{path}:
|
||||
get:
|
||||
summary: get hub script content by path
|
||||
operationId: getHubScriptContentByPath
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: script details
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/scripts/list:
|
||||
get:
|
||||
summary: list all available scripts
|
||||
|
||||
@@ -12,7 +12,7 @@ use sqlx::{query_scalar, Postgres, Transaction};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::js_eval::eval_timeout;
|
||||
use crate::scripts::ScriptLang;
|
||||
use crate::scripts::{get_hub_script_by_path, ScriptLang};
|
||||
use crate::users::create_token_for_owner;
|
||||
use crate::{
|
||||
audit::{audit_log, ActionKind},
|
||||
@@ -167,14 +167,11 @@ pub async fn run_job_by_path(
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
let script_path = script_path.to_path();
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let script_hash = get_latest_hash_for_path(&mut tx, &w_id, script_path).await?;
|
||||
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
|
||||
let (uuid, tx) = push(
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::ScriptHash {
|
||||
hash: script_hash,
|
||||
path: script_path.to_owned(),
|
||||
},
|
||||
job_payload,
|
||||
args,
|
||||
&authed.username,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
@@ -188,6 +185,25 @@ pub async fn run_job_by_path(
|
||||
Ok((StatusCode::CREATED, uuid.to_string()))
|
||||
}
|
||||
|
||||
async fn script_path_to_payload<'c>(
|
||||
script_path: &str,
|
||||
db: &mut Transaction<'c, Postgres>,
|
||||
w_id: &String,
|
||||
) -> Result<JobPayload, Error> {
|
||||
let job_payload = if script_path.starts_with("hub/") {
|
||||
JobPayload::ScriptHub {
|
||||
path: script_path.to_owned(),
|
||||
}
|
||||
} else {
|
||||
let script_hash = get_latest_hash_for_path(db, w_id, script_path).await?;
|
||||
JobPayload::ScriptHash {
|
||||
hash: script_hash,
|
||||
path: script_path.to_owned(),
|
||||
}
|
||||
};
|
||||
Ok(job_payload)
|
||||
}
|
||||
|
||||
pub async fn get_latest_hash_for_path<'c>(
|
||||
db: &mut Transaction<'c, Postgres>,
|
||||
w_id: &str,
|
||||
@@ -816,6 +832,7 @@ enum Job {
|
||||
#[serde(rename_all(serialize = "lowercase"))]
|
||||
pub enum JobKind {
|
||||
Script,
|
||||
Script_Hub,
|
||||
Preview,
|
||||
Dependencies,
|
||||
Flow,
|
||||
@@ -954,6 +971,9 @@ struct PreviewFlow {
|
||||
}
|
||||
|
||||
pub enum JobPayload {
|
||||
ScriptHub {
|
||||
path: String,
|
||||
},
|
||||
ScriptHash {
|
||||
hash: ScriptHash,
|
||||
path: String,
|
||||
@@ -1030,6 +1050,25 @@ pub async fn push<'c>(
|
||||
Some(language),
|
||||
)
|
||||
}
|
||||
JobPayload::ScriptHub { path } => (
|
||||
None,
|
||||
Some(path.clone()),
|
||||
Some(
|
||||
get_hub_script_by_path(
|
||||
Authed {
|
||||
email: Some("".to_string()),
|
||||
username: user.to_string(),
|
||||
is_admin: false,
|
||||
groups: vec![],
|
||||
},
|
||||
Path(StripPath(path)),
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
JobKind::Script_Hub,
|
||||
None,
|
||||
Some(ScriptLang::Deno),
|
||||
),
|
||||
JobPayload::Code(RawCode {
|
||||
content,
|
||||
path,
|
||||
@@ -1446,12 +1485,7 @@ async fn push_next_flow_job(
|
||||
let mut tx = db.begin().await?;
|
||||
let job_payload = match &module.value {
|
||||
FlowModuleValue::Script { path: script_path } => {
|
||||
let script_hash =
|
||||
get_latest_hash_for_path(&mut tx, &job.workspace_id, script_path).await?;
|
||||
JobPayload::ScriptHash {
|
||||
hash: script_hash,
|
||||
path: script_path.to_owned(),
|
||||
}
|
||||
script_path_to_payload(script_path, &mut tx, &job.workspace_id).await?
|
||||
}
|
||||
a @ _ => {
|
||||
tracing::info!("Unrecognized module values {:?}", a);
|
||||
|
||||
@@ -11,7 +11,7 @@ use sql_builder::prelude::*;
|
||||
use crate::{
|
||||
audit::{audit_log, ActionKind},
|
||||
db::{UserDB, DB},
|
||||
error::{Error, JsonResult, Result},
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
jobs, parser,
|
||||
users::{owner_to_token_owner, truncate_token, Authed, Tokened},
|
||||
utils::{require_admin, Pagination, StripPath},
|
||||
@@ -41,6 +41,8 @@ pub fn global_service() -> Router {
|
||||
post(parse_python_code_to_jsonschema),
|
||||
)
|
||||
.route("/deno/tojsonschema", post(parse_deno_code_to_jsonschema))
|
||||
.route("/hub/list", get(list_hub_scripts))
|
||||
.route("/hub/get/*path", get(get_hub_script_by_path))
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -241,6 +243,41 @@ async fn list_scripts(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct SearchData {
|
||||
asks: Vec<ScriptSearch>,
|
||||
}
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct ScriptSearch {
|
||||
id: i32,
|
||||
summary: String,
|
||||
app: String,
|
||||
approved: bool,
|
||||
}
|
||||
|
||||
async fn list_hub_scripts(
|
||||
Authed {
|
||||
email, username, ..
|
||||
}: Authed,
|
||||
) -> JsonResult<Vec<ScriptSearch>> {
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?;
|
||||
let rows = http_client
|
||||
.get("https://hub.windmill.dev/searchData?approved=true")
|
||||
.header("X-email", email.unwrap_or_else(|| "".to_string()))
|
||||
.header("X-username", username)
|
||||
.send()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.json::<SearchData>()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.asks;
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
fn hash_script(ns: &NewScript) -> i64 {
|
||||
let mut dh = DefaultHasher::new();
|
||||
ns.hash(&mut dh);
|
||||
@@ -448,6 +485,34 @@ async fn create_script(
|
||||
Ok((StatusCode::CREATED, format!("{}", hash)))
|
||||
}
|
||||
|
||||
pub async fn get_hub_script_by_path(
|
||||
Authed {
|
||||
email, username, ..
|
||||
}: Authed,
|
||||
Path(path): Path<StripPath>,
|
||||
) -> Result<String> {
|
||||
let path = path
|
||||
.to_path()
|
||||
.strip_prefix("hub/")
|
||||
.ok_or_else(|| Error::BadRequest("Impossible to remove prefix hex".to_string()))?;
|
||||
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?;
|
||||
let content = http_client
|
||||
.get(format!("https://hub.windmill.dev/raw/{path}.ts"))
|
||||
.header("X-email", email.unwrap_or_else(|| "".to_string()))
|
||||
.header("X-username", username)
|
||||
.send()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.text()
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
async fn get_script_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -20,11 +20,15 @@ pub struct Pagination {
|
||||
pub per_page: Option<usize>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct StripPath(String);
|
||||
pub struct StripPath(pub String);
|
||||
|
||||
impl StripPath {
|
||||
pub fn to_path(&self) -> &str {
|
||||
self.0.strip_prefix('/').unwrap()
|
||||
if self.0.starts_with('/') {
|
||||
self.0.strip_prefix('/').unwrap()
|
||||
} else {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -345,6 +345,7 @@ async fn handle_job(
|
||||
}
|
||||
} else {
|
||||
let (inner_content, requirements_o, language) = if matches!(job.job_kind, JobKind::Preview)
|
||||
|| matches!(job.job_kind, JobKind::Script_Hub)
|
||||
{
|
||||
let code = (job.raw_code.as_ref().unwrap_or(&"no raw code".to_owned())).to_owned();
|
||||
let reqs = if job
|
||||
@@ -549,7 +550,7 @@ print(res_json)
|
||||
let wrapper_content: String = format!(
|
||||
r#"
|
||||
import {{ main }} from "./inner.ts";
|
||||
const {{{spread}}}= JSON.parse(`{ser_args}`);
|
||||
const {{{spread}}} = JSON.parse(`{ser_args}`);
|
||||
|
||||
async function run() {{
|
||||
let res: any = await main({spread});
|
||||
|
||||
@@ -13,7 +13,7 @@ export {
|
||||
*/
|
||||
export function createConf(): Configuration & { workspace_id: string } {
|
||||
const token = Deno.env.get("WM_TOKEN") ?? 'no_token'
|
||||
const base_url = Deno.env.get("BASE_URL") ?? 'http://localhost:8000'
|
||||
const base_url = Deno.env.get("BASE_INTERNAL_URL") ?? 'http://localhost:8000'
|
||||
return {
|
||||
...createConfiguration({
|
||||
baseServer: new ServerConfiguration(`${base_url}/api`, {}),
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
import { page } from '$app/stores'
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast'
|
||||
import { onMount } from 'svelte'
|
||||
import { UserService, WorkspaceService } from '../gen'
|
||||
import { WorkspaceService } from '../gen'
|
||||
import { superadmin, userStore, usersWorkspaceStore, workspaceStore } from '../stores'
|
||||
import {
|
||||
clearStores,
|
||||
superadmin,
|
||||
usernameStore,
|
||||
usersWorkspaceStore,
|
||||
workspaceStore
|
||||
} from '../stores'
|
||||
import { getUser, logout, logoutWithRedirect, refreshSuperadmin, sendUserToast } from '../utils'
|
||||
getUserExt,
|
||||
logout,
|
||||
logoutWithRedirect,
|
||||
refreshSuperadmin,
|
||||
sendUserToast
|
||||
} from '../utils'
|
||||
|
||||
// Default toast options
|
||||
const toastOptions = {
|
||||
@@ -31,15 +31,20 @@
|
||||
'Connection got disposed.'
|
||||
]
|
||||
|
||||
async function loadData() {
|
||||
async function loadUser() {
|
||||
try {
|
||||
$usersWorkspaceStore = await WorkspaceService.listUserWorkspaces()
|
||||
await refreshSuperadmin()
|
||||
|
||||
if ($workspaceStore && $usernameStore) {
|
||||
await getUser($workspaceStore)
|
||||
} else if ($superadmin) {
|
||||
console.log('You are a superadmin, you can go wherever you please')
|
||||
if ($workspaceStore) {
|
||||
if ($userStore) {
|
||||
console.log(`Welcome ${$userStore.email}`)
|
||||
} else if ($superadmin) {
|
||||
console.log('You are a superadmin, you can go wherever you please')
|
||||
} else {
|
||||
$userStore = await getUserExt($workspaceStore)
|
||||
throw Error('Not logged in')
|
||||
}
|
||||
} else {
|
||||
goto('/user/workspaces')
|
||||
}
|
||||
@@ -49,7 +54,7 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadData()
|
||||
loadUser()
|
||||
|
||||
window.onunhandledrejection = (event: PromiseRejectionEvent) => {
|
||||
event.preventDefault()
|
||||
|
||||
@@ -21,14 +21,8 @@
|
||||
import { onMount } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import '../app.css'
|
||||
import { OpenAPI } from '../gen'
|
||||
import {
|
||||
superadmin,
|
||||
usernameStore,
|
||||
userStore,
|
||||
usersWorkspaceStore,
|
||||
workspaceStore
|
||||
} from '../stores'
|
||||
import { OpenAPI, ScriptService } from '../gen'
|
||||
import { hubScripts, superadmin, userStore, usersWorkspaceStore, workspaceStore } from '../stores'
|
||||
import { clickOutside, logout } from '../utils'
|
||||
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
@@ -56,10 +50,20 @@
|
||||
workspacePickerOpen = false
|
||||
}
|
||||
|
||||
async function loadSearchData() {
|
||||
const scripts = await ScriptService.listHubScripts()
|
||||
$hubScripts = scripts.map((x) => ({
|
||||
path: `hub/${x.id}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
|
||||
summary: `${x.summary} (${x.app})`,
|
||||
approved: x.approved
|
||||
}))
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
|
||||
//Mobile
|
||||
isCollapsed = isMobile
|
||||
loadSearchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -179,7 +183,7 @@
|
||||
<div class="mx-auto">
|
||||
<span class:hidden={isCollapsed} class="px-2 font-mono text-xs whitespace-nowrap">
|
||||
<Icon class="text-white" data={faUser} scale={0.6} />
|
||||
{$usernameStore ?? $superadmin ?? '___'}
|
||||
{$userStore?.username ?? $superadmin ?? '___'}
|
||||
{#if $userStore?.is_admin}
|
||||
<Icon class="text-white" data={faCrown} scale={0.6} />
|
||||
{/if}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { displayDate, sendUserToast } from '../utils'
|
||||
import { goto } from '$app/navigation'
|
||||
import PageHeader from './components/PageHeader.svelte'
|
||||
import { usernameStore, userStore, workspaceStore } from '../stores'
|
||||
import { userStore, workspaceStore } from '../stores'
|
||||
import TableCustom from './components/TableCustom.svelte'
|
||||
import CenteredPage from './components/CenteredPage.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
@@ -81,9 +81,7 @@
|
||||
loadUsers()
|
||||
loadLogs(username, pageIndex)
|
||||
}
|
||||
if ($usernameStore) {
|
||||
username = $usernameStore
|
||||
}
|
||||
username = $userStore?.username
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore } from '../../stores'
|
||||
import { userStore, workspaceStore } from '../../stores'
|
||||
|
||||
import Modal from '../../routes/components/Modal.svelte'
|
||||
import { type Group, GroupService, UserService } from '../../gen'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import PageHeader from './PageHeader.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { canWrite, getUser } from '../../utils'
|
||||
import { canWrite } from '../../utils'
|
||||
|
||||
let name = ''
|
||||
let modal: Modal
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
if (group && $workspaceStore) {
|
||||
if (group && $workspaceStore && userStore) {
|
||||
members = (group.members ?? []).map((x) => {
|
||||
return {
|
||||
name: x,
|
||||
@@ -49,8 +49,7 @@
|
||||
|
||||
async function loadGroup(): Promise<void> {
|
||||
group = await GroupService.getGroup({ workspace: $workspaceStore!, name })
|
||||
const user = await getUser($workspaceStore!)
|
||||
can_write = canWrite(group.name!, group.extra_perms ?? {}, user)
|
||||
can_write = canWrite(group.name!, group.extra_perms ?? {}, $userStore)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="w-12/12 pb-4">
|
||||
<input placeholder="Search {itemName}" bind:value={itemsFilter} class="search-item" />
|
||||
</div>
|
||||
<ul role="list" class="divide-y divide-gray-200">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{#each filteredItems as obj}
|
||||
<li
|
||||
class="py-4 px-1 gap-1 flex flex-col hover:bg-white hover:border text-black cursor-pointer"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import ScriptPicker from './ScriptPicker.svelte'
|
||||
import { emptySchema } from '../../utils'
|
||||
import FlowPreview from './FlowPreview.svelte'
|
||||
import { inferArgs } from '../../infer'
|
||||
|
||||
export let flow: Flow
|
||||
export let i: number
|
||||
@@ -19,16 +20,24 @@
|
||||
|
||||
export async function loadSchema() {
|
||||
if (mod.value.path) {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: mod.value.path ?? ''
|
||||
})
|
||||
let schema
|
||||
if (mod.value.path.startsWith('hub/')) {
|
||||
const code = await ScriptService.getHubScriptContentByPath({ path: mod.value.path })
|
||||
schema = emptySchema()
|
||||
await inferArgs('deno', code, schema)
|
||||
} else {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: mod.value.path ?? ''
|
||||
})
|
||||
schema = script.schema
|
||||
}
|
||||
if (
|
||||
JSON.stringify(Object.keys(script.schema?.properties ?? {}).sort()) !=
|
||||
JSON.stringify(Object.keys(schema?.properties ?? {}).sort()) !=
|
||||
JSON.stringify(Object.keys(mod.input_transform).sort())
|
||||
) {
|
||||
let it = {}
|
||||
Object.keys(script.schema?.properties ?? {}).map(
|
||||
Object.keys(schema?.properties ?? {}).map(
|
||||
(x) =>
|
||||
(it[x] = {
|
||||
type: 'static',
|
||||
@@ -37,7 +46,7 @@
|
||||
)
|
||||
schemaForms[i]?.setArgs(it)
|
||||
}
|
||||
schemas[i] = script.schema ?? emptySchema()
|
||||
schemas[i] = schema ?? emptySchema()
|
||||
} else {
|
||||
schemaForms[i]?.setArgs({})
|
||||
schemas[i] = emptySchema()
|
||||
@@ -66,7 +75,7 @@
|
||||
</div>
|
||||
<div class="p-10">
|
||||
<h2 class="mb-4">Step script</h2>
|
||||
<ScriptPicker bind:scriptPath={mod.value.path} on:select={loadSchema} />
|
||||
<ScriptPicker allowHub={true} bind:scriptPath={mod.value.path} on:select={loadSchema} />
|
||||
<div class="my-4" />
|
||||
<h2 class="mb-4">Step inputs</h2>
|
||||
<SchemaForm
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<fieldset class="mt-2 mr-4">
|
||||
<fieldset>
|
||||
<legend class="sr-only">{label}</legend>
|
||||
<div class="flex flex-row gap-2">
|
||||
{#each options as [label, val]}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast } from '../../utils'
|
||||
import { ScriptService, FlowService } from '../../gen'
|
||||
import { ScriptService, FlowService, Script } from '../../gen'
|
||||
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import { hubScripts, workspaceStore } from '../../stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ItemPicker from './ItemPicker.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
export let scriptPath: string | undefined = undefined
|
||||
export let allowFlow = false
|
||||
export let isFlow = false
|
||||
export let allowHub = false
|
||||
export let itemKind: 'hub' | 'script' | 'flow' = allowHub ? 'hub' : 'script'
|
||||
|
||||
let items: { summary: String; path: String; version?: String }[] = []
|
||||
let itemPicker: ItemPicker
|
||||
@@ -24,23 +25,33 @@
|
||||
let code: string = ''
|
||||
let lang: 'deno' | 'python3' | undefined
|
||||
|
||||
let options: [[string, any]] = [['Script', 'script']]
|
||||
allowHub && options.unshift(['Hub', 'hub'])
|
||||
allowFlow && options.push(['Flow', 'flow'])
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getScript() {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: scriptPath!
|
||||
})
|
||||
code = script.content
|
||||
lang = script.language
|
||||
if (itemKind == 'hub') {
|
||||
code = await ScriptService.getHubScriptContentByPath({ path: scriptPath! })
|
||||
lang = Script.language.DENO
|
||||
} else {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: scriptPath!
|
||||
})
|
||||
code = script.content
|
||||
lang = script.language
|
||||
}
|
||||
}
|
||||
|
||||
async function loadItems(isFlow: boolean): Promise<void> {
|
||||
async function loadItems(): Promise<void> {
|
||||
try {
|
||||
if (isFlow) {
|
||||
if (itemKind == 'flow') {
|
||||
items = await FlowService.listFlows({ workspace: $workspaceStore! })
|
||||
} else {
|
||||
} else if (itemKind == 'script') {
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore! })
|
||||
} else {
|
||||
items = $hubScripts ?? []
|
||||
}
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load items: ${err}`, true)
|
||||
@@ -49,7 +60,7 @@
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadItems(isFlow)
|
||||
loadItems()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -62,38 +73,23 @@
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, _) => {
|
||||
scriptPath = path
|
||||
dispatch('select', { path: scriptPath })
|
||||
}}
|
||||
itemName={isFlow ? 'Flow' : 'Script'}
|
||||
itemName={itemKind == 'flow' ? 'Flow' : 'Script'}
|
||||
extraField="summary"
|
||||
loadItems={async () => {
|
||||
return items
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row items-center">
|
||||
{#if allowFlow}
|
||||
<RadioButton
|
||||
bind:value={isFlow}
|
||||
options={[
|
||||
['Script', false],
|
||||
['Flow', true]
|
||||
]}
|
||||
/>
|
||||
<div class="flex flex-row items-center space-x-5">
|
||||
{#if options.length > 1}
|
||||
<RadioButton bind:value={itemKind} {options} />
|
||||
{/if}
|
||||
<select
|
||||
bind:value={scriptPath}
|
||||
on:change={() => {
|
||||
dispatch('select', { path: scriptPath })
|
||||
}}
|
||||
class="max-w-lg"
|
||||
>
|
||||
<option value={undefined} />
|
||||
{#each items as s}
|
||||
<option value={s.path}>{s.path} {s.summary ? ' | ' + s.summary : ''}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button on:click={() => itemPicker.openModal()}
|
||||
><Icon class="mx-4 text-gray-700 text-opacity-70" data={faSearch} /></button
|
||||
|
||||
<input type="text" value={scriptPath ?? 'No path chosen yet'} disabled />
|
||||
<button class="default-button text-gray-100" on:click={() => itemPicker.openModal()}
|
||||
>Pick a {itemKind} path<Icon class="mx-4" data={faSearch} /></button
|
||||
>
|
||||
{#if scriptPath != undefined && scriptPath != ''}
|
||||
<button
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import Tooltip from './components/Tooltip.svelte'
|
||||
import ShareModal from './components/ShareModal.svelte'
|
||||
import SharedBadge from './components/SharedBadge.svelte'
|
||||
import { superadmin, usernameStore, userStore, workspaceStore } from '../stores'
|
||||
import { superadmin, userStore, workspaceStore } from '../stores'
|
||||
import CenteredPage from './components/CenteredPage.svelte'
|
||||
import Tabs from './components/Tabs.svelte'
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
let defaults: string[] = []
|
||||
|
||||
if (tab == 'all' || tab == 'personal') {
|
||||
defaults = defaults.concat(`u/${$usernameStore}`)
|
||||
defaults = defaults.concat(`u/${$userStore?.username}`)
|
||||
}
|
||||
if (tab == 'all' || tab == 'groups') {
|
||||
defaults = defaults.concat($userStore?.groups.map((x) => `g/${x}`) ?? [])
|
||||
@@ -65,7 +65,7 @@
|
||||
function tabFromPath(path: string) {
|
||||
let t: Tab = 'shared'
|
||||
let path_prefix = path.split('/').slice(0, 2)
|
||||
if (path_prefix[0] == 'u' && path_prefix[1] == $usernameStore) {
|
||||
if (path_prefix[0] == 'u' && path_prefix[1] == $userStore?.username) {
|
||||
t = 'personal'
|
||||
} else if (path_prefix[0] == 'g' && $userStore?.groups.includes(path_prefix[1])) {
|
||||
t = 'groups'
|
||||
@@ -118,7 +118,7 @@
|
||||
<Tabs
|
||||
tabs={[
|
||||
['all', 'all'],
|
||||
['personal', `personal space (${$usernameStore})`],
|
||||
['personal', `personal space (${$userStore?.username})`],
|
||||
['groups', 'groups'],
|
||||
['shared', 'shared']
|
||||
]}
|
||||
@@ -131,7 +131,7 @@
|
||||
<div class="shadow p-4 my-2">
|
||||
{#if sectionTab == 'personal'}
|
||||
<h2 class="">
|
||||
My personal space ({`u/${$usernameStore}`})
|
||||
My personal space ({`u/${$userStore?.username}`})
|
||||
</h2>
|
||||
<p class="italic text-xs text-gray-600 mb-4">
|
||||
All flows owned by you (and visible only to you if you do not explicitely share them)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { FlowService, type Flow } from '../../../gen'
|
||||
import { sendUserToast, displayDaysAgo, canWrite, getUser } from '../../../utils'
|
||||
import { sendUserToast, displayDaysAgo, canWrite } from '../../../utils'
|
||||
import Icon from 'svelte-awesome'
|
||||
import {
|
||||
faPlay,
|
||||
@@ -17,7 +17,7 @@
|
||||
import github from 'svelte-highlight/styles/github'
|
||||
import Tooltip from '../../components/Tooltip.svelte'
|
||||
import ShareModal from '../../components/ShareModal.svelte'
|
||||
import { workspaceStore } from '../../../stores'
|
||||
import { userStore, workspaceStore } from '../../../stores'
|
||||
import SharedBadge from '../../components/SharedBadge.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import SchemaViewer from '../../components/SchemaViewer.svelte'
|
||||
@@ -31,7 +31,7 @@
|
||||
let shareModal: ShareModal
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadFlow(path)
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,7 @@
|
||||
|
||||
async function loadFlow(hash: string): Promise<void> {
|
||||
flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path })
|
||||
const user = await getUser($workspaceStore!)
|
||||
can_write = canWrite(flow.path, flow.extra_perms!, user)
|
||||
can_write = canWrite(flow.path, flow.extra_perms!, $userStore)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { canWrite, getUser } from '../utils'
|
||||
import { canWrite } from '../utils'
|
||||
import { GroupService } from '../gen'
|
||||
import type { Group } from '../gen'
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import ShareModal from './components/ShareModal.svelte'
|
||||
import SharedBadge from './components/SharedBadge.svelte'
|
||||
import { faEdit, faPlus, faShare } from '@fortawesome/free-solid-svg-icons'
|
||||
import { workspaceStore } from '../stores'
|
||||
import { userStore, workspaceStore } from '../stores'
|
||||
import CenteredPage from './components/CenteredPage.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import GroupModal from './components/GroupModal.svelte'
|
||||
@@ -22,9 +22,8 @@
|
||||
let groupModal: GroupModal
|
||||
|
||||
async function loadGroups(): Promise<void> {
|
||||
const user = await getUser($workspaceStore!)
|
||||
groups = (await GroupService.listGroups({ workspace: $workspaceStore! })).map((x) => {
|
||||
return { canWrite: canWrite(x.name, x.extra_perms ?? {}, user), ...x }
|
||||
return { canWrite: canWrite(x.name, x.extra_perms ?? {}, $userStore), ...x }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadGroups()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { canWrite, emptySchema, getUser, sendUserToast } from '../utils'
|
||||
import { canWrite, emptySchema, sendUserToast } from '../utils'
|
||||
import { ResourceService } from '../gen'
|
||||
import type { Resource, ResourceType } from '../gen'
|
||||
import PageHeader from './components/PageHeader.svelte'
|
||||
@@ -40,16 +40,13 @@
|
||||
|
||||
let resourceEditor: ResourceEditor | undefined
|
||||
|
||||
let user: UserExt | undefined
|
||||
$: user = $userStore
|
||||
|
||||
let shareModal: ShareModal
|
||||
|
||||
async function loadResources(): Promise<void> {
|
||||
const user = await getUser($workspaceStore!)
|
||||
resources = (await ResourceService.listResource({ workspace: $workspaceStore! })).map((x) => {
|
||||
return {
|
||||
canWrite: canWrite(x.path, x.extra_perms!, user) && $workspaceStore! == x.workspace_id,
|
||||
canWrite:
|
||||
canWrite(x.path, x.extra_perms!, $userStore) && $workspaceStore! == x.workspace_id,
|
||||
...x
|
||||
}
|
||||
})
|
||||
@@ -105,7 +102,7 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadResources()
|
||||
loadResourceTypes()
|
||||
}
|
||||
@@ -245,7 +242,7 @@
|
||||
on:click={() => {
|
||||
handleDeleteResourceType(name)
|
||||
}}
|
||||
disabled={!(user?.is_admin ?? false)}
|
||||
disabled={!($userStore?.is_admin ?? false)}
|
||||
/>
|
||||
{/if}
|
||||
</td>
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
|
||||
let script_path = $page.url.searchParams.get('path') || ''
|
||||
let is_flow = $page.url.searchParams.get('isFlow') == 'true'
|
||||
let itemKind: 'flow' | 'script' = is_flow ? 'flow' : 'script'
|
||||
|
||||
$: is_flow = itemKind == 'flow'
|
||||
|
||||
let runnable: Script | Flow | undefined
|
||||
let args: Record<string, any> = {}
|
||||
@@ -163,7 +166,7 @@
|
||||
<p class="text-xs text-gray-600">
|
||||
Pick a script or flow to be triggered by the schedule<Required required={true} />
|
||||
</p>
|
||||
<ScriptPicker allowFlow={true} bind:isFlow={is_flow} bind:scriptPath={script_path} />
|
||||
<ScriptPicker allowFlow={true} bind:itemKind bind:scriptPath={script_path} />
|
||||
<div class="max-w-5xl {edit ? '' : 'mt-2 md:mt-6'}">
|
||||
<h2>Arguments</h2>
|
||||
{#if runnable}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast, truncateHash, displayDate, canWrite, getUser } from '../utils'
|
||||
import { sendUserToast, displayDate, canWrite } from '../utils'
|
||||
import { type Schedule, ScheduleService } from '../gen'
|
||||
|
||||
import PageHeader from './components/PageHeader.svelte'
|
||||
@@ -15,7 +15,7 @@
|
||||
faToggleOff,
|
||||
faToggleOn
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { workspaceStore } from '../stores'
|
||||
import { userStore, workspaceStore } from '../stores'
|
||||
import CenteredPage from './components/CenteredPage.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
let shareModal: ShareModal
|
||||
|
||||
async function loadSchedules(): Promise<void> {
|
||||
const user = await getUser($workspaceStore!)
|
||||
schedules = (await ScheduleService.listSchedules({ workspace: $workspaceStore! })).map((x) => {
|
||||
return { canWrite: canWrite(x.path, x.extra_perms!, user), ...x }
|
||||
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -46,7 +45,7 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadSchedules()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { Script } from '../gen'
|
||||
import { ScriptService } from '../gen'
|
||||
import { superadmin, usernameStore, userStore, workspaceStore } from '../stores'
|
||||
import { superadmin, userStore, workspaceStore } from '../stores'
|
||||
import { canWrite, groupBy, sendUserToast, truncateHash } from '../utils'
|
||||
import Badge from './components/Badge.svelte'
|
||||
import CenteredPage from './components/CenteredPage.svelte'
|
||||
@@ -62,7 +62,7 @@
|
||||
let defaults: string[] = []
|
||||
|
||||
if (tab == 'all' || tab == 'personal') {
|
||||
defaults = defaults.concat(`u/${$usernameStore}`)
|
||||
defaults = defaults.concat(`u/${$userStore?.username}`)
|
||||
}
|
||||
if (tab == 'all' || tab == 'groups') {
|
||||
defaults = defaults.concat($userStore?.groups.map((x) => `g/${x}`) ?? [])
|
||||
@@ -86,7 +86,7 @@
|
||||
function tabFromPath(path: string) {
|
||||
let t: Tab = 'shared'
|
||||
let path_prefix = path.split('/').slice(0, 2)
|
||||
if (path_prefix[0] == 'u' && path_prefix[1] == $usernameStore) {
|
||||
if (path_prefix[0] == 'u' && path_prefix[1] == $userStore?.username) {
|
||||
t = 'personal'
|
||||
} else if (path_prefix[0] == 'g' && $userStore?.groups.includes(path_prefix[1])) {
|
||||
t = 'groups'
|
||||
@@ -154,7 +154,7 @@
|
||||
<Tabs
|
||||
tabs={[
|
||||
['all', 'all'],
|
||||
['personal', `personal space (${$usernameStore})`],
|
||||
['personal', `personal space (${$userStore?.username})`],
|
||||
['groups', 'groups'],
|
||||
['shared', 'shared'],
|
||||
['community', 'community']
|
||||
@@ -168,7 +168,7 @@
|
||||
<div class="shadow p-4 my-2">
|
||||
{#if sectionTab == 'personal'}
|
||||
<h2 class="">
|
||||
My personal space ({`u/${$usernameStore}`})
|
||||
My personal space ({`u/${$userStore?.username}`})
|
||||
</h2>
|
||||
<p class="italic text-xs text-gray-600 mb-4">
|
||||
All scripts owned by you (and visible only to you if you do not explicitely share them)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { ScriptService, type Script } from '../../../gen'
|
||||
import { truncateHash, sendUserToast, displayDaysAgo, canWrite, getUser } from '../../../utils'
|
||||
import { truncateHash, sendUserToast, displayDaysAgo, canWrite } from '../../../utils'
|
||||
import Icon from 'svelte-awesome'
|
||||
import {
|
||||
faPlay,
|
||||
@@ -83,9 +83,9 @@
|
||||
script = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: hash })
|
||||
hash = script.hash
|
||||
}
|
||||
const user = await getUser($workspaceStore!)
|
||||
can_write =
|
||||
script.workspace_id == $workspaceStore && canWrite(script.path, script.extra_perms!, user)
|
||||
script.workspace_id == $workspaceStore &&
|
||||
canWrite(script.path, script.extra_perms!, $userStore)
|
||||
if (script.path && script.archived) {
|
||||
const script_by_path = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { slide } from 'svelte/transition'
|
||||
import { UserService, WorkspaceService } from '../../gen'
|
||||
import { clearStores, userStore, usersWorkspaceStore, workspaceStore } from '../../stores'
|
||||
import { getUser, refreshSuperadmin, sendUserToast } from '../../utils'
|
||||
import { refreshSuperadmin, sendUserToast } from '../../utils'
|
||||
import CenteredModal from './CenteredModal.svelte'
|
||||
|
||||
let email = $page.url.searchParams.get('email') ?? ''
|
||||
@@ -28,25 +28,32 @@
|
||||
|
||||
// Once logged in, we can fetch the workspaces
|
||||
$usersWorkspaceStore = await WorkspaceService.listUserWorkspaces()
|
||||
// And the actual user
|
||||
$userStore = await getUser($workspaceStore!)
|
||||
// trigger a reload of the user
|
||||
$workspaceStore = $workspaceStore
|
||||
// Finally, we check whether the user is a superadmin
|
||||
refreshSuperadmin()
|
||||
redirectUser()
|
||||
} catch (err) {
|
||||
sendUserToast(`Cannot login: ${err.body}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
function redirectUser() {
|
||||
if ($workspaceStore) {
|
||||
if (rd) {
|
||||
goto(decodeURI(rd))
|
||||
} else {
|
||||
goto('/user/workspaces')
|
||||
goto('/')
|
||||
}
|
||||
} catch (err) {
|
||||
sendUserToast(`Cannot login: ${err.body}`, true)
|
||||
} else {
|
||||
goto('/user/workspaces')
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await UserService.getCurrentEmail()
|
||||
goto('/')
|
||||
redirectUser()
|
||||
} catch {
|
||||
clearStores()
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"
|
||||
on:click={() => {
|
||||
workspaceStore.set(workspace.id)
|
||||
|
||||
goto('/')
|
||||
}}
|
||||
><span class="font-mono">{workspace.id}</span> - {workspace.name} as
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { canWrite, getUser, sendUserToast } from '../utils'
|
||||
import { canWrite, sendUserToast } from '../utils'
|
||||
import { VariableService } from '../gen'
|
||||
import type { ListableVariable, ContextualVariable } from '../gen'
|
||||
import Dropdown from './components/Dropdown.svelte'
|
||||
@@ -9,7 +9,7 @@
|
||||
import ShareModal from './components/ShareModal.svelte'
|
||||
import SharedBadge from './components/SharedBadge.svelte'
|
||||
import VariableEditor from './components/VariableEditor.svelte'
|
||||
import { workspaceStore } from './../stores'
|
||||
import { userStore, workspaceStore } from './../stores'
|
||||
import CenteredPage from './components/CenteredPage.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
@@ -24,10 +24,9 @@
|
||||
|
||||
// If relative, the dropdown is positioned relative to its button
|
||||
async function loadVariables(): Promise<void> {
|
||||
const user = await getUser($workspaceStore!)
|
||||
variables = (await VariableService.listVariable({ workspace: $workspaceStore! })).map((x) => {
|
||||
return {
|
||||
canWrite: canWrite(x.path, x.extra_perms!, user) && x.workspace_id == $workspaceStore,
|
||||
canWrite: canWrite(x.path, x.extra_perms!, $userStore) && x.workspace_id == $workspaceStore,
|
||||
...x
|
||||
}
|
||||
})
|
||||
@@ -45,7 +44,7 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadVariables()
|
||||
loadContextualVariables()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { browser } from '$app/env'
|
||||
import type { Readable } from 'svelte/store'
|
||||
import { derived, writable } from 'svelte/store'
|
||||
import { writable } from 'svelte/store'
|
||||
import type { UserWorkspaceList } from './gen'
|
||||
import { getUserExt } from './utils'
|
||||
|
||||
export interface UserExt {
|
||||
email: string
|
||||
@@ -11,27 +11,31 @@ export interface UserExt {
|
||||
groups: string[]
|
||||
pgroups: string[]
|
||||
}
|
||||
|
||||
let persistedWorkspace = browser && localStorage.getItem('workspace')
|
||||
|
||||
export const userStore = writable<UserExt | undefined>(undefined)
|
||||
export let workspaceStore = writable<string | undefined>(
|
||||
export const workspaceStore = writable<string | undefined>(
|
||||
persistedWorkspace ? String(persistedWorkspace) : undefined
|
||||
)
|
||||
export const usersWorkspaceStore = writable<UserWorkspaceList | undefined>(undefined)
|
||||
export const usernameStore: Readable<string | undefined> = derived(
|
||||
[usersWorkspaceStore, workspaceStore],
|
||||
($values, set) => {
|
||||
set($values[0]?.workspaces.find((x) => x.id == $values[1])?.username)
|
||||
}
|
||||
)
|
||||
export const superadmin = writable<String | false | undefined>(undefined)
|
||||
export const hubScripts = writable<Array<{
|
||||
path: string
|
||||
summary: string
|
||||
approved: boolean
|
||||
}> | undefined>(undefined)
|
||||
|
||||
if (browser) {
|
||||
workspaceStore.subscribe((workspace) => {
|
||||
workspaceStore.subscribe(async (workspace) => {
|
||||
if (workspace) {
|
||||
localStorage.setItem('workspace', String(workspace))
|
||||
userStore.set(await getUserExt(workspace))
|
||||
} else {
|
||||
userStore.set(undefined)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export function clearStores(): void {
|
||||
|
||||
@@ -69,53 +69,20 @@ export function truncateHash(hash: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStore(workspace: string): Promise<UserExt | undefined> {
|
||||
export async function getUserExt(workspace: string): Promise<UserExt | undefined> {
|
||||
try {
|
||||
const user = await UserService.whoami({ workspace })
|
||||
const nuser = mapUserToUserExt(user)
|
||||
userStore.set(nuser)
|
||||
return nuser
|
||||
return mapUserToUserExt(user)
|
||||
} catch (error) {
|
||||
userStore.set(undefined)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUser(workspace: string): Promise<UserExt | undefined> {
|
||||
const user = get(userStore)
|
||||
if (user === undefined) {
|
||||
return loadStore(workspace)
|
||||
} else {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
export function logoutWithRedirect(rd?: string): void {
|
||||
const error = encodeURIComponent('You have been logged out because your session has expired.')
|
||||
goto(`/user/login?error=${error}${rd ? '&rd=' + encodeURIComponent(rd) : ''}`)
|
||||
}
|
||||
|
||||
export async function handle401<T>(
|
||||
promise: CancelablePromise<T> | Promise<T>,
|
||||
rd?: string
|
||||
): Promise<T> {
|
||||
// Redirects to login if the `promise` returns a 401 due to lack of authentication
|
||||
// Optionnally provide `rd`, to which the user will be redirected after logging back in
|
||||
|
||||
return promise.catch(async (error) => {
|
||||
if (error.status === 401) {
|
||||
if (getUser(get(workspaceStore)!) === undefined) {
|
||||
logoutWithRedirect(rd)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return null as any
|
||||
} else {
|
||||
throw Error('You do not have enough privilege to access this')
|
||||
}
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
@@ -141,8 +108,8 @@ export async function refreshSuperadmin(): Promise<void> {
|
||||
export async function logout(logoutMessage?: string): Promise<void> {
|
||||
try {
|
||||
clearStores()
|
||||
goto(`/user/login${logoutMessage ? '?error=' + encodeURIComponent(logoutMessage) : ''}`)
|
||||
await UserService.logout()
|
||||
goto(`/user/login${logoutMessage ? '?error=' + encodeURIComponent(logoutMessage) : ''}`)
|
||||
sendUserToast('you have been logged out')
|
||||
} catch (error) {
|
||||
goto(
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "es2020",
|
||||
"lib": ["es2020", "DOM"],
|
||||
"target": "es2020",
|
||||
"lib": ["es2021", "DOM"],
|
||||
"target": "es2021",
|
||||
/**
|
||||
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||
to enforce using \`import type\` instead of \`import\` for Types.
|
||||
|
||||
@@ -30,7 +30,7 @@ class JobStatus(Enum):
|
||||
_client: AuthenticatedClient | None = None
|
||||
|
||||
def create_client(base_url: str | None = None, token: str | None = None) -> AuthenticatedClient:
|
||||
env_base_url = os.environ.get("BASE_URL")
|
||||
env_base_url = os.environ.get("BASE_INTERNAL_URL")
|
||||
|
||||
if env_base_url is not None:
|
||||
env_base_url = env_base_url + "/api"
|
||||
|
||||
Reference in New Issue
Block a user