mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 08:01:35 +00:00
fix: consistent exists/{resource} addition + usage in frontend
This commit is contained in:
+110
-8
@@ -379,10 +379,10 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/workspaces/validate_id:
|
||||
/workspaces/exists:
|
||||
post:
|
||||
summary: validate id
|
||||
operationId: validateId
|
||||
summary: exists workspace
|
||||
operationId: existsWorkspace
|
||||
tags:
|
||||
- workspace
|
||||
requestBody:
|
||||
@@ -403,12 +403,12 @@ paths:
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
type: boolean
|
||||
|
||||
/workspaces/validate_username:
|
||||
/workspaces/exists_username:
|
||||
post:
|
||||
summary: validate username
|
||||
operationId: validateUsername
|
||||
summary: exists username
|
||||
operationId: existsUsername
|
||||
tags:
|
||||
- workspace
|
||||
requestBody:
|
||||
@@ -431,7 +431,7 @@ paths:
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
type: boolean
|
||||
|
||||
/users/email:
|
||||
get:
|
||||
@@ -934,6 +934,23 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ListableVariable"
|
||||
|
||||
/w/{workspace}/variables/exists/{path}:
|
||||
get:
|
||||
summary: does variable exists at path
|
||||
operationId: existsVariable
|
||||
tags:
|
||||
- variable
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: variable
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/variables/list:
|
||||
get:
|
||||
summary: list variables
|
||||
@@ -1304,6 +1321,23 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Resource"
|
||||
|
||||
/w/{workspace}/resources/exists/{path}:
|
||||
get:
|
||||
summary: does resource exists
|
||||
operationId: existsResource
|
||||
tags:
|
||||
- resource
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: does resource exists
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/resources/list:
|
||||
get:
|
||||
summary: list resources
|
||||
@@ -1410,6 +1444,23 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ResourceType"
|
||||
|
||||
/w/{workspace}/resources/type/exists/{path}:
|
||||
get:
|
||||
summary: does resource_type exists
|
||||
operationId: existsResourceType
|
||||
tags:
|
||||
- resource
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: does resource_type exist
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/resources/type/list:
|
||||
get:
|
||||
summary: list resource_types
|
||||
@@ -1731,6 +1782,23 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Script"
|
||||
|
||||
/w/{workspace}/scripts/exists/p/{path}:
|
||||
get:
|
||||
summary: exists script by path
|
||||
operationId: existsScriptByPath
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: does it exists
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/scripts/get/h/{hash}:
|
||||
get:
|
||||
summary: get script by hash
|
||||
@@ -1906,6 +1974,23 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Flow"
|
||||
|
||||
/w/{workspace}/flows/exists/{path}:
|
||||
get:
|
||||
summary: exists flow by path
|
||||
operationId: existsFlowByPath
|
||||
tags:
|
||||
- flow
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: flow details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/flows/create:
|
||||
post:
|
||||
summary: create flow
|
||||
@@ -2462,6 +2547,23 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Schedule"
|
||||
|
||||
/w/{workspace}/schedules/exists/{path}:
|
||||
get:
|
||||
summary: does schedule exists
|
||||
operationId: existsSchedule
|
||||
tags:
|
||||
- schedule
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: schedule deleted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/schedules/list:
|
||||
get:
|
||||
summary: list schedules
|
||||
|
||||
@@ -62,6 +62,27 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"0784bb86a503f02b9ef247a2b83a82ddfa49632552b223a9f4536a449b0a1eb8": {
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM resource WHERE path = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"09246e9ff5b2beb61ab51a5f73d980f7638904d5a18a415e52d5e1c94dffd0aa": {
|
||||
"query": "SELECT SUM(duration) FROM completed_job WHERE created_by = $1 AND created_at > NOW() - INTERVAL '1200 seconds' AND workspace_id = $2",
|
||||
"describe": {
|
||||
@@ -681,6 +702,27 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"355dcb2cbebd13f0e3bdd4929b9e431b0e6d72716d1c4f9ab6af6adce5b5e4b3": {
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM resource_type WHERE name = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"37d3ee8009055e869941e548a6d5a352053a5d7782f662c34b94706488abccb6": {
|
||||
"query": "UPDATE queue SET running = false WHERE last_ping < $1 RETURNING id",
|
||||
"describe": {
|
||||
@@ -1691,6 +1733,27 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"88a3f58a1a315200fdd2e4bb8638246ee21818f8aaaf56f6e9d7ddce1490d886": {
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM variable WHERE path = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"88b7589a6416da8be4b26af3bf30fcfcd6aeae7bc5a37e9a735cabbe2691c570": {
|
||||
"query": "SELECT login_type::TEXT FROM password WHERE email = $1",
|
||||
"describe": {
|
||||
@@ -2095,6 +2158,27 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"a38059dc3574da498ce986c916b6d385b1f18d5bd659ef13c43fafa9daff6bda": {
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter'))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"a98b2d68f023f46ab91167d3147416df672c2aed2ba5ab70e98a9da5fa47255a": {
|
||||
"query": "INSERT INTO workspace_settings\n (workspace_id)\n VALUES ($1)",
|
||||
"describe": {
|
||||
@@ -2931,6 +3015,27 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"e587299612077e6de39b5a501aa8a736cae4a21f11853f8ae66a610b4e65f49e": {
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter') AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"e94abd39ec51b7e0c48c190d47ed766fd4f401187c3b60b3e599426c95232f7f": {
|
||||
"query": "UPDATE queue SET last_ping = $1 WHERE id = $2",
|
||||
"describe": {
|
||||
@@ -2944,6 +3049,27 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"ee99cb974fb35d2d92fa96cecb53b8ad3a729ac710f362dc55a576613b66f9be": {
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM schedule WHERE path = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"f056b5f3e66a764748925f1bfd3180923fde8c7fdf69088d0e4a5555cc049545": {
|
||||
"query": "SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
|
||||
+20
-1
@@ -20,7 +20,7 @@ use sqlx::FromRow;
|
||||
|
||||
use crate::{
|
||||
audit::{audit_log, ActionKind},
|
||||
db::UserDB,
|
||||
db::{UserDB, DB},
|
||||
error::{Error, JsonResult, Result},
|
||||
jobs::RawCode,
|
||||
scripts::Schema,
|
||||
@@ -35,6 +35,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/update/*path", post(update_flow))
|
||||
.route("/archive/*path", post(archive_flow_by_path))
|
||||
.route("/get/*path", get(get_flow_by_path))
|
||||
.route("/exists/*path", get(exists_flow_by_path))
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize)]
|
||||
@@ -271,6 +272,24 @@ async fn get_flow_by_path(
|
||||
Ok(Json(flow))
|
||||
}
|
||||
|
||||
async fn exists_flow_by_path(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<bool> {
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter'))",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
async fn archive_flow_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
+6
-2
@@ -79,9 +79,10 @@ pub async fn initialize_tracing() -> anyhow::Result<()> {
|
||||
tracing_init::initialize_tracing().await
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BaseUrl(String);
|
||||
|
||||
struct CloudHosted(bool);
|
||||
|
||||
pub async fn run_server(
|
||||
db: DB,
|
||||
addr: SocketAddr,
|
||||
@@ -115,7 +116,10 @@ pub async fn run_server(
|
||||
.layer(Extension(user_db))
|
||||
.layer(Extension(auth_cache.clone()))
|
||||
.layer(Extension(basic_clients))
|
||||
.layer(Extension(BaseUrl(base_url.to_string())))
|
||||
.layer(Extension(Arc::new(BaseUrl(base_url.to_string()))))
|
||||
.layer(Extension(Arc::new(CloudHosted(
|
||||
std::env::var("CLOUD_HOSTED").is_ok(),
|
||||
))))
|
||||
.layer(Extension(http_client))
|
||||
.layer(CookieManagerLayer::new());
|
||||
// build our application with a route
|
||||
|
||||
@@ -597,7 +597,7 @@ async fn slack_command(
|
||||
SlackSig { sig, ts }: SlackSig,
|
||||
Extension(slack_verifier): Extension<Arc<Option<SlackVerifier>>>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(base_url): Extension<BaseUrl>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
body: Bytes,
|
||||
) -> error::Result<String> {
|
||||
let form: SlackCommand = serde_urlencoded::from_bytes(&body)
|
||||
@@ -651,7 +651,7 @@ async fn slack_command(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
let url = base_url.0;
|
||||
let url = base_url.0.to_owned();
|
||||
return Ok(format!("Job launched. See details at {url}/run/{uuid}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list", get(list_resources))
|
||||
.route("/get/*path", get(get_resource))
|
||||
.route("/exists/*path", get(exists_resource))
|
||||
.route("/get_value/*path", get(get_resource_value))
|
||||
.route("/update/*path", post(update_resource))
|
||||
.route("/delete/*path", delete(delete_resource))
|
||||
@@ -33,6 +34,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/type/list", get(list_resource_types))
|
||||
.route("/type/listnames", get(list_resource_types_names))
|
||||
.route("/type/get/:name", get(get_resource_type))
|
||||
.route("/type/exists/:name", get(exists_resource_type))
|
||||
.route("/type/update/:name", post(update_resource_type))
|
||||
.route("/type/delete/:name", delete(delete_resource_type))
|
||||
.route("/type/create", post(create_resource_type))
|
||||
@@ -150,6 +152,24 @@ async fn get_resource(
|
||||
Ok(Json(resource))
|
||||
}
|
||||
|
||||
async fn exists_resource(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<bool> {
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM resource WHERE path = $1 AND workspace_id = $2)",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
async fn get_resource_value(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -325,6 +345,22 @@ async fn get_resource_type(
|
||||
Ok(Json(resource_type))
|
||||
}
|
||||
|
||||
async fn exists_resource_type(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
) -> JsonResult<bool> {
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM resource_type WHERE name = $1 AND workspace_id = $2)",
|
||||
name,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
async fn create_resource_type(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
+20
-1
@@ -9,7 +9,7 @@ use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
audit::{audit_log, ActionKind},
|
||||
db::UserDB,
|
||||
db::{UserDB, DB},
|
||||
error::{self, JsonResult, Result},
|
||||
jobs::{self, push, JobPayload},
|
||||
users::Authed,
|
||||
@@ -30,6 +30,7 @@ pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list", get(list_schedule))
|
||||
.route("/get/*path", get(get_schedule))
|
||||
.route("/exists/*path", get(exists_schedule))
|
||||
.route("/create", post(create_schedule))
|
||||
.route("/update/*path", post(edit_schedule))
|
||||
.route("/setenabled/*path", post(set_enabled))
|
||||
@@ -284,6 +285,24 @@ async fn get_schedule(
|
||||
Ok(Json(schedule))
|
||||
}
|
||||
|
||||
async fn exists_schedule(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<bool> {
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM schedule WHERE path = $1 AND workspace_id = $2)",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PreviewPayload {
|
||||
pub schedule: String,
|
||||
|
||||
+22
-1
@@ -17,7 +17,7 @@ use crate::{
|
||||
utils::{require_admin, Pagination, StripPath},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
extract::{Extension, Host, Path, Query},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
@@ -51,6 +51,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/create", post(create_script))
|
||||
.route("/archive/p/*path", post(archive_script_by_path))
|
||||
.route("/get/p/*path", get(get_script_by_path))
|
||||
.route("/exists/p/*path", get(exists_script_by_path))
|
||||
.route("/archive/h/:hash", post(archive_script_by_hash))
|
||||
.route("/delete/h/:hash", post(delete_script_by_hash))
|
||||
.route("/get/h/:hash", get(get_script_by_hash))
|
||||
@@ -263,6 +264,7 @@ async fn list_hub_scripts(
|
||||
Authed {
|
||||
email, username, ..
|
||||
}: Authed,
|
||||
Host(host): Host,
|
||||
) -> JsonResult<Vec<ScriptSearch>> {
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
@@ -272,6 +274,7 @@ async fn list_hub_scripts(
|
||||
.get("https://hub.windmill.dev/searchData?approved=true")
|
||||
.header("X-email", email.unwrap_or_else(|| "".to_string()))
|
||||
.header("X-username", username)
|
||||
.header("X-hostname", host)
|
||||
.send()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
@@ -540,6 +543,24 @@ async fn get_script_by_path(
|
||||
Ok(Json(script))
|
||||
}
|
||||
|
||||
async fn exists_script_by_path(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<bool> {
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter') AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')))",
|
||||
path, w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
async fn get_script_by_hash_internal<'c>(
|
||||
db: &mut Transaction<'c, Postgres>,
|
||||
workspace_id: &str,
|
||||
|
||||
@@ -32,6 +32,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/list", get(list_variables))
|
||||
.route("/list_contextual", get(list_contextual_variables))
|
||||
.route("/get/*path", get(get_variable))
|
||||
.route("/exists/*path", get(exists_variable))
|
||||
.route("/update/*path", post(update_variable))
|
||||
.route("/delete/*path", delete(delete_variable))
|
||||
.route("/create", post(create_variable))
|
||||
@@ -254,6 +255,24 @@ async fn get_variable(
|
||||
Ok(Json(r))
|
||||
}
|
||||
|
||||
async fn exists_variable(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<bool> {
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM variable WHERE path = $1 AND workspace_id = $2)",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
async fn create_variable(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -41,10 +41,7 @@ pub fn global_service() -> Router {
|
||||
.route("/users", get(user_workspaces))
|
||||
.route("/create", post(create_workspace))
|
||||
.route("/exists", post(exists_workspace))
|
||||
.route("/validate_username", post(validate_username))
|
||||
.route("/validate_id", post(validate_id))
|
||||
|
||||
|
||||
.route("/exists_username", post(exists_username))
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize)]
|
||||
@@ -485,7 +482,7 @@ async fn delete_invite(
|
||||
))
|
||||
}
|
||||
|
||||
async fn validate_username(
|
||||
async fn exists_username(
|
||||
Extension(db): Extension<DB>,
|
||||
Json(vu): Json<ValidateUsername>,
|
||||
) -> Result<String> {
|
||||
@@ -506,27 +503,6 @@ async fn validate_username(
|
||||
Ok("valid username".to_string())
|
||||
}
|
||||
|
||||
|
||||
async fn validate_id(
|
||||
Extension(db): Extension<DB>,
|
||||
Json(wi): Json<WorkspaceId>,
|
||||
) -> Result<String> {
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM workspace WHERE id = $1)",
|
||||
wi.id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(true);
|
||||
|
||||
if exists {
|
||||
return Err(Error::BadRequest("id already taken".to_string()))
|
||||
}
|
||||
|
||||
Ok("valid workspace".to_string())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ScriptMetadata {
|
||||
summary: String,
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
let step = 1
|
||||
|
||||
let no_back = false
|
||||
|
||||
let pathError = ''
|
||||
|
||||
export function open() {
|
||||
step = 1
|
||||
value = ''
|
||||
@@ -72,27 +75,18 @@
|
||||
} else if (step == 1 && !manual) {
|
||||
window.location.href = `/api/oauth/connect/${resource_type}?scopes=${scopes.join('+')}`
|
||||
} else {
|
||||
let exists = true
|
||||
try {
|
||||
await VariableService.getVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path
|
||||
})
|
||||
} catch (e) {
|
||||
exists = false
|
||||
}
|
||||
let exists = await VariableService.existsVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path
|
||||
})
|
||||
if (exists) {
|
||||
throw Error(`Variable at path ${path} already exists. Delete it or pick another path`)
|
||||
}
|
||||
exists = true
|
||||
try {
|
||||
await ResourceService.getResource({
|
||||
workspace: $workspaceStore!,
|
||||
path
|
||||
})
|
||||
} catch (e) {
|
||||
exists = false
|
||||
}
|
||||
exists = await ResourceService.existsResource({
|
||||
workspace: $workspaceStore!,
|
||||
path
|
||||
})
|
||||
|
||||
if (exists) {
|
||||
throw Error(`Resource at path ${path} already exists. Delete it or pick another path`)
|
||||
}
|
||||
@@ -235,7 +229,12 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Path bind:path initialPath={`u/${$userStore?.username ?? ''}/my_${resource_type}`} />
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
initialPath={`u/${$userStore?.username ?? ''}/my_${resource_type}`}
|
||||
kind="resource"
|
||||
/>
|
||||
<ul class="mt-10 bg-white">
|
||||
<li>
|
||||
1. A secret variable containing the token <span class="font-bold"
|
||||
@@ -266,7 +265,8 @@
|
||||
<button
|
||||
class="default-button px-4 py-2 font-semibold"
|
||||
class:default-button-disabled={(step == 1 && resource_type == '') ||
|
||||
(step == 2 && value == '')}
|
||||
(step == 2 && value == '') ||
|
||||
(step == 3 && pathError != '')}
|
||||
on:click={next}
|
||||
>
|
||||
{step == 3 ? 'Connect' : 'Next'}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/stores'
|
||||
import { FlowService, ScriptService, type Flow } from '$lib/gen'
|
||||
import { clearPreviewResults, hubScripts, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import { pathIsEmpty, sendUserToast } from '$lib/utils'
|
||||
import { onMount } from 'svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import FlowEditor from './FlowEditor.svelte'
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
let mode: FlowMode
|
||||
|
||||
let pathError = ''
|
||||
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1
|
||||
|
||||
async function loadSearchData() {
|
||||
@@ -92,6 +94,7 @@
|
||||
}}>Step 1: Metadata</button
|
||||
>
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="{step === 2
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
@@ -100,6 +103,7 @@
|
||||
}}>Step 2: Flow</button
|
||||
>
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="{step === 3
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
@@ -111,8 +115,7 @@
|
||||
<div class="flex flex-row-reverse ml-2">
|
||||
{#if step != 3}
|
||||
<button
|
||||
disabled={step == 1 &&
|
||||
(flow.path == undefined || flow.path == '' || flow.path.split('/')[2] == '')}
|
||||
disabled={step == 1 && pathError != ''}
|
||||
class="default-button px-6 max-h-8"
|
||||
on:click={() => {
|
||||
changeStep(step + 1)
|
||||
@@ -141,7 +144,13 @@
|
||||
<!-- metadata -->
|
||||
{#if step === 1}
|
||||
<div class="grid grid-cols-1 gap-6 max-w-7xl">
|
||||
<Path bind:path={flow.path} {initialPath} namePlaceholder="example/my/flow">
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path={flow.path}
|
||||
{initialPath}
|
||||
namePlaceholder="example/my/flow"
|
||||
kind="flow"
|
||||
>
|
||||
<div slot="ownerToolkit" class="text-gray-700 text-2xs">
|
||||
Flow permissions depend on their path. Select the group <span class="font-mono">all</span>
|
||||
to share your flow, and <span class="font-mono">user</span> to keep it private.
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { type Meta, pathToMeta } from '$lib/common'
|
||||
|
||||
import type { Group } from '$lib/gen'
|
||||
import {
|
||||
FlowService,
|
||||
ResourceService,
|
||||
ScheduleService,
|
||||
ScriptService,
|
||||
VariableService,
|
||||
type Group
|
||||
} from '$lib/gen'
|
||||
import { GroupService } from '$lib/gen'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { sleep } from '$lib/utils'
|
||||
import { workspace } from 'vscode'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule'
|
||||
export let meta: Meta = {
|
||||
ownerKind: 'user',
|
||||
owner: '',
|
||||
@@ -15,9 +24,11 @@
|
||||
export let namePlaceholder = ''
|
||||
export let initialPath: string
|
||||
export let path = ''
|
||||
export let error = ''
|
||||
|
||||
export let kind: PathKind
|
||||
|
||||
let groups: Group[] = []
|
||||
let error = ''
|
||||
|
||||
$: {
|
||||
path = [meta.ownerKind === 'group' ? 'g' : 'u', meta.owner, meta.name].join('/')
|
||||
@@ -26,6 +37,7 @@
|
||||
export function getPath() {
|
||||
return path
|
||||
}
|
||||
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
meta.ownerKind = 'user'
|
||||
@@ -40,23 +52,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: validateName(meta)
|
||||
$: validate(meta, path, kind)
|
||||
|
||||
async function loadGroups(): Promise<void> {
|
||||
groups = await GroupService.listGroups({ workspace: $workspaceStore! })
|
||||
meta.owner = meta.owner
|
||||
}
|
||||
|
||||
function validateName(meta: Meta): void {
|
||||
async function validate(meta: Meta, path: string, kind: PathKind) {
|
||||
validateName(meta) && (await validatePath(path, kind))
|
||||
}
|
||||
|
||||
let validateTimeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
async function validatePath(path: string, kind: PathKind): Promise<void> {
|
||||
if (initialPath == '' && initialPath != path) {
|
||||
if (validateTimeout) {
|
||||
clearTimeout(validateTimeout)
|
||||
}
|
||||
validateTimeout = setTimeout(async () => {
|
||||
if (
|
||||
(kind == 'flow' &&
|
||||
(await FlowService.existsFlowByPath({ workspace: $workspaceStore!, path: path }))) ||
|
||||
(kind == 'script' &&
|
||||
(await ScriptService.existsScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'resource' &&
|
||||
(await ResourceService.existsResource({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'variable' &&
|
||||
(await VariableService.existsVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'schedule' &&
|
||||
(await ScheduleService.existsSchedule({ workspace: $workspaceStore!, path: path })))
|
||||
) {
|
||||
error = 'path already used'
|
||||
} else if (validateName(meta)) {
|
||||
error = ''
|
||||
}
|
||||
validateTimeout = undefined
|
||||
}, 500)
|
||||
} else {
|
||||
error = ''
|
||||
}
|
||||
}
|
||||
|
||||
function validateName(meta: Meta): boolean {
|
||||
if (meta.name == undefined || meta.name == '') {
|
||||
error = 'choose a name'
|
||||
return
|
||||
}
|
||||
const regex = new RegExp(/^[\w-]+(\/[\w-]+)*$/)
|
||||
if (regex.test(meta.name)) {
|
||||
error = ''
|
||||
return false
|
||||
} else if (!/^[\w-]+(\/[\w-]+)*$/.test(meta.name)) {
|
||||
error = 'This name is not valid.'
|
||||
return false
|
||||
} else {
|
||||
error = 'This name is not valid. '
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
let path = ''
|
||||
let initialPath = ''
|
||||
let pathError = ''
|
||||
|
||||
let step = 1
|
||||
|
||||
@@ -126,7 +127,13 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
<div>
|
||||
<span class="text-purple-500 text-2xs grow">{error ?? ''}</span>
|
||||
<span class="mb-1 font-semibold text-gray-700">Path</span>
|
||||
<Path bind:path {initialPath} namePlaceholder="my/resource">
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="my/resource"
|
||||
kind="resource"
|
||||
>
|
||||
<div slot="ownerToolkit" class="text-gray-700 text-2xs">
|
||||
Resource permissions depend on their path. Select the group <span class="font-mono"
|
||||
>all</span
|
||||
@@ -195,19 +202,16 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
</div>
|
||||
<span slot="submission">
|
||||
{#if step === 1}
|
||||
{#if selectedResourceType && path != undefined && path != '' && path.split('/')[2] != ''}
|
||||
<button
|
||||
class="default-button px-4 py-2 font-semibold"
|
||||
on:click={async () => {
|
||||
await loadResourceType()
|
||||
step = 2
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
{:else}
|
||||
<button class="default-button-disabled px-4 py-2 font-semibold">Next</button>
|
||||
{/if}
|
||||
<button
|
||||
class="default-button px-4 py-2 font-semibold"
|
||||
on:click={async () => {
|
||||
await loadResourceType()
|
||||
step = 2
|
||||
}}
|
||||
disabled={selectedResourceType == undefined || pathError != ''}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="default-button-secondary px-4 py-2 font-semibold"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { ScriptService, type Script } from '$lib/gen'
|
||||
|
||||
import { emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { emptySchema, pathIsEmpty, sendUserToast } from '$lib/utils'
|
||||
import { onDestroy } from 'svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import { page } from '$app/stores'
|
||||
@@ -22,6 +22,8 @@
|
||||
export let script: Script
|
||||
export let initialPath: string = ''
|
||||
|
||||
let pathError = ''
|
||||
|
||||
$: {
|
||||
$page.url.searchParams.set('state', btoa(JSON.stringify(script)))
|
||||
history.replaceState({}, '', $page.url)
|
||||
@@ -100,6 +102,7 @@
|
||||
}}>Step 1: Metadata</button
|
||||
>
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="{step === 2
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
@@ -108,6 +111,7 @@
|
||||
}}>Step 2: Code</button
|
||||
>
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="{step === 3
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
@@ -119,8 +123,7 @@
|
||||
<div class="flex flex-row-reverse ml-2">
|
||||
{#if step != 3}
|
||||
<button
|
||||
disabled={step == 1 &&
|
||||
(script.path == undefined || script.path == '' || script.path.split('/')[2] == '')}
|
||||
disabled={step == 1 && pathError != ''}
|
||||
class="default-button px-6 max-h-8"
|
||||
on:click={() => {
|
||||
changeStep(step + 1)
|
||||
@@ -152,7 +155,13 @@
|
||||
<!-- metadata -->
|
||||
{#if step === 1}
|
||||
<div class="grid grid-cols-1 gap-6 max-w-7xl">
|
||||
<Path bind:path={script.path} {initialPath} namePlaceholder="example/my/script">
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path={script.path}
|
||||
{initialPath}
|
||||
namePlaceholder="example/my/script"
|
||||
kind="script"
|
||||
>
|
||||
<div slot="ownerToolkit" class="text-gray-700 text-2xs">
|
||||
Script permissions depend on their path. Select the group <span class="font-mono"
|
||||
>all</span
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
let modal: Modal
|
||||
let edit = false
|
||||
let initialPath: string
|
||||
let pathError = ''
|
||||
|
||||
export function initNew(): void {
|
||||
variable = {
|
||||
@@ -122,7 +123,13 @@
|
||||
<div class="flex flex-col gap-2 ">
|
||||
<div>
|
||||
<div class="text-gray-700 mb-0 pb-0">path</div>
|
||||
<Path bind:path {initialPath} namePlaceholder="my/variable" />
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="my/variable"
|
||||
kind="variable"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label class="block pb-6">
|
||||
@@ -160,7 +167,7 @@
|
||||
createVariable()
|
||||
}
|
||||
}}
|
||||
disabled={!valid}
|
||||
disabled={!valid || pathError != ''}
|
||||
>
|
||||
{#if edit}
|
||||
Save
|
||||
|
||||
@@ -190,6 +190,10 @@ export function elapsedSinceSecs(date: string): number {
|
||||
return Math.round((new Date().getTime() - new Date(date).getTime()) / 1000)
|
||||
}
|
||||
|
||||
export function pathIsEmpty(path: string): boolean {
|
||||
return path == undefined || path.split('/')[2] == ''
|
||||
}
|
||||
|
||||
export function groupBy<T>(
|
||||
scripts: T[],
|
||||
toGroup: (t: T) => string,
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
let preview: string[] = []
|
||||
|
||||
let path: string = ''
|
||||
let pathError = ''
|
||||
|
||||
const offset = new Date().getTimezoneOffset()
|
||||
|
||||
@@ -143,7 +144,13 @@
|
||||
{#if !edit}
|
||||
<h2>Save schedule as</h2>
|
||||
|
||||
<Path bind:path {initialPath} namePlaceholder={'my/schedule'}>
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder={'my/schedule'}
|
||||
kind="schedule"
|
||||
>
|
||||
<div slot="ownerToolkit" class="text-gray-700 text-2xs">
|
||||
Schedule permissions depend on their path. Select the group <span class="font-mono"
|
||||
>all</span
|
||||
@@ -213,8 +220,8 @@
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!allowSchedule}
|
||||
class="{allowSchedule ? 'default-button' : 'default-button-disabled'} w-min px-6"
|
||||
disabled={!allowSchedule || pathError != ''}
|
||||
class="default-button w-min px-6"
|
||||
on:click={scheduleScript}
|
||||
>
|
||||
{edit ? 'Save' : 'Schedule'}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
async function validateName(username: string): Promise<void> {
|
||||
try {
|
||||
await WorkspaceService.validateUsername({ requestBody: { id: workspace_id, username } })
|
||||
await WorkspaceService.existsUsername({ requestBody: { id: workspace_id, username } })
|
||||
errorUsername = ''
|
||||
} catch {
|
||||
errorUsername = 'username already exists'
|
||||
|
||||
@@ -21,13 +21,16 @@
|
||||
$: validateName(id)
|
||||
|
||||
async function validateName(id: string): Promise<void> {
|
||||
try {
|
||||
await WorkspaceService.validateId({ requestBody: { id } })
|
||||
errorId = ''
|
||||
} catch {
|
||||
let exists = await WorkspaceService.existsWorkspace({ requestBody: { id } })
|
||||
if (exists) {
|
||||
errorId = 'id already exists'
|
||||
} else if (id != '' && !/^\w+(-\w+)*$/.test(id)) {
|
||||
errorId = 'id can only contain letters, numbers and dashes and must not finish by a dash'
|
||||
} else {
|
||||
errorId = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function createWorkspace(): Promise<void> {
|
||||
await WorkspaceService.createWorkspace({
|
||||
requestBody: {
|
||||
|
||||
Reference in New Issue
Block a user