feat: add extra metadata to folders/resources/types + path prefix filtering for all + improve groups UI (#3936)

* feat: add extra metadata to folders/schedules/resources/types

* feat: add path prefix filter to api

* chore: updatee ref

* feat: improve groups UI

* fix: nit

* fix: nit
This commit is contained in:
HugoCasa
2024-06-22 01:29:23 +02:00
committed by GitHub
parent e92d8655c4
commit 6933f06f1c
55 changed files with 585 additions and 318 deletions
@@ -22,6 +22,16 @@
"ordinal": 3,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
}
],
"parameters": {
@@ -34,6 +44,8 @@
false,
false,
true,
true,
true,
true
]
},
@@ -35,31 +35,41 @@
},
{
"ordinal": 6,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "is_expired",
"type_info": "Bool"
},
{
"ordinal": 7,
"ordinal": 9,
"name": "is_refreshed",
"type_info": "Bool"
},
{
"ordinal": 8,
"ordinal": 10,
"name": "refresh_error",
"type_info": "Text"
},
{
"ordinal": 9,
"ordinal": 11,
"name": "is_linked",
"type_info": "Bool"
},
{
"ordinal": 10,
"ordinal": 12,
"name": "is_oauth?",
"type_info": "Bool"
},
{
"ordinal": 11,
"ordinal": 13,
"name": "account",
"type_info": "Int4"
}
@@ -77,6 +87,8 @@
true,
false,
false,
true,
true,
null,
null,
true,
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, edited_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -9,10 +9,11 @@
"Varchar",
"Jsonb",
"Text",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "489a62b5943a7a21ce487aa7b72a63dfc6300dd93bc29f5ec4cb1bfc471ad0bf"
"hash": "42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4"
}
@@ -32,6 +32,16 @@
"ordinal": 5,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 6,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "created_by",
"type_info": "Varchar"
}
],
"parameters": {
@@ -45,7 +55,9 @@
true,
true,
false,
false
false,
true,
true
]
},
"hash": "45e4d13f5806122faecdb1d9ab18159555b652869a036b006f4a151e999b17b7"
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE resource SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "5eb1e072a6cc6ace25a5806b5adfdc0ae5c89c556daf491f39e629951a912c6c"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, $2, $3, $4, $5)",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, now())",
"describe": {
"columns": [],
"parameters": {
@@ -9,10 +9,12 @@
"Varchar",
"Varchar",
"VarcharArray",
"Jsonb"
"Jsonb",
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "e33d0bec7e08c0bb4e6f26c93a636707714699e8984b9f537085e773849c8b43"
"hash": "5f3f1f1ca72b0392f227d22adef9ef795049e60a823e8ce754a3f9d91ef1b6bb"
}
@@ -5,7 +5,7 @@
"columns": [
{
"ordinal": 0,
"name": "?column?",
"name": "bool",
"type_info": "Bool"
}
],
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "6e6d781fe63602541f7cb4806d5ddde0792e1861ea8761b4875677c3d0594a86"
}
@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, description = $4, resource_type = $5",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Jsonb",
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "726f77c2c1ef63eece7a1176bcbb916987fc29b04b889544a064dc7cd5eab36f"
}
@@ -22,6 +22,16 @@
"ordinal": 3,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
}
],
"parameters": {
@@ -33,6 +43,8 @@
false,
false,
true,
true,
true,
true
]
},
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "7ded28aae42230da6e3aa6fa952268e8e15cc8b16770c20ec6be2a9e82ab93c9"
}
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "98c8e2c58b38beedcb474251c2554a2f1b768ec7ed1a419313f4842f26425e5d"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
"query": "SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
@@ -27,6 +27,21 @@
"ordinal": 4,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 5,
"name": "summary",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "edited_at",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -41,8 +56,11 @@
false,
false,
false,
false
false,
true,
true,
true
]
},
"hash": "8543f029d9784234e4c6a6dcd7b03e62d544b98be261334ee210594e0bb839f2"
"hash": "9c6d44ffae63b4050ef3a66cb05d5a4ee50dea4b30093df2e0ceb684201ca4b0"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder\n (workspace_id, name, display_name, owners, extra_perms)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING",
"query": "INSERT INTO folder\n (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
@@ -9,10 +9,11 @@
"Varchar",
"Varchar",
"VarcharArray",
"Jsonb"
"Jsonb",
"Varchar"
]
},
"nullable": []
},
"hash": "f2aee3ae39c90e40dd1835befc339e5381cc9104933cf8e90d840a9bf638ff52"
"hash": "ad3b0176e3d41a0c1d79a8eb59da2a9cc4fee3473b3f6ea69f397d91e9356ba1"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "b7ac3383a058ab3bbd7280300cebc5f81eeb9e96622ad2557f810a8b91641b89"
}
@@ -22,6 +22,16 @@
"ordinal": 3,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
}
],
"parameters": {
@@ -33,6 +43,8 @@
false,
false,
true,
true,
true,
true
]
},
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4",
"query": "UPDATE resource SET path = $1, value = $2, edited_at = now() WHERE path = $3 AND workspace_id = $4",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "8c0131a9cc61f2daa258d49767242bcaab6bb34a977ff7fb0c18aa9202d11f47"
"hash": "be10033b37dc9cbc38cc56afa8ef93cd7dbe267525d78cbfea87bb7c7cbf8ec4"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE folder SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "c2a6e8f38d2293c45ede26b89a976f1a32f5bc2323a28f640cf3301a299f1cf4"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3",
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, edited_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -8,10 +8,11 @@
"Varchar",
"Varchar",
"Jsonb",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "ad9ccde8d831461f1f312d530867714577a825cd40d6655ed61b0b343b5d4482"
"hash": "c4ee16065fa021cf6443c2f29f1ca986b9730eae9df26dd33c3ea21e35df62c4"
}
@@ -0,0 +1,19 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Jsonb",
"Text",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE name = $1 AND workspace_id = $2",
"query": "SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE name = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
@@ -27,6 +27,21 @@
"ordinal": 4,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 5,
"name": "summary",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "edited_at",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -40,8 +55,11 @@
false,
false,
false,
false
false,
true,
true,
true
]
},
"hash": "3911bf3bbc82d87366a5297496fd2350a252bcd69f1c7c972bf566ee7eb28b0a"
"hash": "cb20f04352364f112ec564617722354577dc5f77169fd4573317454e47361d65"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource (workspace_id, path, value, description, resource_type, created_by, edited_at) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme', $2, now()) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "cccf9e216d84dfbb801b92d697496f44d93b344190a26c8393867b3a268b9aba"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE resource_type SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "d1b508e1d8496e5e7aee9db555f9740128d87fe5183f9b7b72f550a5502e1cbb"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description)\n VALUES ($1, $2, $3, $4)",
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now())",
"describe": {
"columns": [],
"parameters": {
@@ -8,10 +8,11 @@
"Varchar",
"Varchar",
"Jsonb",
"Text"
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "5061c0d054bf4f028e7fe51a8f9389024c6ae4492755cadac0f7167e5300bda0"
"hash": "d249343a1d9fce2f7ca9e6e447eb32d7d11b61acdab80dda24db4a9ba3e1f7e6"
}
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4"
}
@@ -22,6 +22,16 @@
"ordinal": 3,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
}
],
"parameters": {
@@ -31,6 +41,8 @@
false,
false,
true,
true,
true,
true
]
},
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "f581f5ad5c386cef1037e3e8dcb759164e21a72ddc859304421ccb96233fa277"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE resource SET value = $1 WHERE path = $2 AND workspace_id = $3",
"query": "UPDATE resource SET value = $1, edited_at = now() WHERE path = $2 AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
@@ -12,5 +12,5 @@
},
"nullable": []
},
"hash": "8876fa929ffb175cd976a2bca1195704aa9fe7215013ae29e49ef15cb201ba57"
"hash": "fd790067c02d384aead5ff8f94efb37cc20d2f7a3749d21a1de5642e888b5f42"
}
+1 -1
View File
@@ -1 +1 @@
6aa8832322866e9f6ff90979db9b2b8f4bf6d4ce
9ef2d9c4aebfee15935634274a2c7c685331a865
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,4 @@
-- Add up migration script here
alter table resource add column edited_at timestamptz, add column created_by varchar(50);
alter table resource_type add column edited_at timestamptz, add column created_by varchar(50);
alter table folder add column summary text, add column edited_at timestamptz, add column created_by varchar(50);
+38
View File
@@ -2373,6 +2373,10 @@ paths:
- variable
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: path_start
in: query
schema:
type: string
responses:
"200":
description: variable list
@@ -2854,6 +2858,10 @@ paths:
in: query
schema:
type: string
- name: path_start
in: query
schema:
type: string
responses:
"200":
description: resource list
@@ -6440,6 +6448,10 @@ paths:
in: query
schema:
type: boolean
- name: path_start
in: query
schema:
type: string
responses:
"200":
description: schedule list
@@ -6917,6 +6929,8 @@ paths:
properties:
name:
type: string
summary:
type: string
owners:
type: array
items:
@@ -6951,6 +6965,8 @@ paths:
schema:
type: object
properties:
summary:
type: string
owners:
type: array
items:
@@ -9842,6 +9858,11 @@ components:
type: object
additionalProperties:
type: boolean
created_by:
type: string
edited_at:
type: string
format: date-time
required:
- path
- resource_type
@@ -9875,6 +9896,11 @@ components:
type: boolean
account:
type: number
created_by:
type: string
edited_at:
type: string
format: date-time
required:
- path
- resource_type
@@ -9892,6 +9918,11 @@ components:
schema: {}
description:
type: string
created_by:
type: string
edited_at:
type: string
format: date-time
required:
- name
@@ -10126,6 +10157,13 @@ components:
type: object
additionalProperties:
type: boolean
summary:
type: string
created_by:
type: string
edited_at:
type: string
format: date-time
required:
- name
- owners
+8
View File
@@ -279,6 +279,14 @@ async fn list_apps(
sqlb.and_where_is_not_null("favorite.path");
}
if let Some(path_start) = &lq.path_start {
sqlb.and_where_like_left("app.path", path_start);
}
if let Some(path_exact) = &lq.path_exact {
sqlb.and_where_eq("app.path", "?".bind(path_exact));
}
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
let mut tx = user_db.begin(&authed).await?;
let rows = sqlx::query_as::<_, ListableApp>(&sql)
+2 -2
View File
@@ -143,10 +143,10 @@ async fn list_flows(
sqlb.and_where_eq("archived", lq.show_archived.unwrap_or(false));
if let Some(ps) = &lq.path_start {
sqlb.and_where_like_left("path", "?".bind(ps));
sqlb.and_where_like_left("o.path", ps);
}
if let Some(p) = &lq.path_exact {
sqlb.and_where_eq("path", "?".bind(p));
sqlb.and_where_eq("o.path", "?".bind(p));
}
if let Some(cb) = &lq.edited_by {
sqlb.and_where_eq("edited_by", "?".bind(cb));
+16 -3
View File
@@ -56,11 +56,15 @@ pub struct Folder {
pub display_name: String,
pub owners: Vec<String>,
pub extra_perms: serde_json::Value,
pub summary: Option<String>,
pub created_by: Option<String>,
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Deserialize)]
pub struct NewFolder {
pub name: String,
pub summary: Option<String>,
pub display_name: Option<String>,
pub owners: Option<Vec<String>>,
pub extra_perms: Option<serde_json::Value>,
@@ -68,6 +72,7 @@ pub struct NewFolder {
#[derive(Deserialize)]
pub struct UpdateFolder {
pub summary: Option<String>,
pub display_name: Option<String>,
pub owners: Option<Vec<String>>,
pub extra_perms: Option<serde_json::Value>,
@@ -90,7 +95,7 @@ async fn list_folders(
let rows = sqlx::query_as!(
Folder,
"SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
"SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
w_id,
per_page as i64,
offset as i64
@@ -199,12 +204,14 @@ async fn create_folder(
sqlx::query_as!(
Folder,
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, $2, $3, $4, $5)",
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, now())",
w_id,
ng.name,
ng.display_name.unwrap_or(ng.name.clone()),
&owners,
extra_perms,
ng.summary,
authed.username
)
.execute(&mut *tx)
.await?;
@@ -283,6 +290,12 @@ async fn update_folder(
sqlb.set("display_name", "?".bind(&display_name));
}
if let Some(summary) = ng.summary {
sqlb.set("summary", "?".bind(&summary));
}
sqlb.set("edited_at", "now()");
if !authed.is_admin {
let prefixed_username = format!("u/{}", authed.username);
if ng.owners.as_ref().is_some_and(|x| {
@@ -380,7 +393,7 @@ pub async fn get_folderopt<'c>(
) -> Result<Option<Folder>> {
let folderopt = sqlx::query_as!(
Folder,
"SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE name = $1 AND workspace_id = $2",
"SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE name = $1 AND workspace_id = $2",
name,
w_id
)
+8
View File
@@ -102,6 +102,14 @@ async fn list_apps(
sqlb.and_where_is_not_null("favorite.path");
}
if let Some(path_start) = &lq.path_start {
sqlb.and_where_like_left("app.path", path_start);
}
if let Some(path_exact) = &lq.path_exact {
sqlb.and_where_eq("app.path", "?".bind(path_exact));
}
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
let mut tx = user_db.begin(&authed).await?;
let rows = sqlx::query_as::<_, ListableApp>(&sql)
+24 -6
View File
@@ -69,6 +69,8 @@ pub struct ResourceType {
pub name: String,
pub schema: Option<serde_json::Value>,
pub description: Option<String>,
pub created_by: Option<String>,
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Deserialize)]
@@ -92,6 +94,8 @@ pub struct Resource {
pub description: Option<String>,
pub resource_type: String,
pub extra_perms: serde_json::Value,
pub created_by: Option<String>,
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(FromRow, Serialize, Deserialize)]
@@ -102,6 +106,8 @@ pub struct ListableResource {
pub description: Option<String>,
pub resource_type: String,
pub extra_perms: serde_json::Value,
pub created_by: Option<String>,
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
pub is_linked: Option<bool>,
pub is_refreshed: Option<bool>,
pub is_oauth: Option<bool>,
@@ -128,6 +134,7 @@ struct EditResource {
pub struct ListResourceQuery {
resource_type: Option<String>,
resource_type_exclude: Option<String>,
path_start: Option<String>,
}
#[derive(Serialize, FromRow)]
@@ -209,6 +216,8 @@ async fn list_resources(
"variable.is_oauth",
"variable.account",
"account.refresh_error",
"resource.created_by",
"resource.edited_at",
])
.left()
.join("variable")
@@ -241,6 +250,10 @@ async fn list_resources(
}
}
if let Some(path_start) = &lq.path_start {
sqlb.and_where_like_left("resource.path", path_start);
}
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
let mut tx = user_db.begin(&authed).await?;
let rows = sqlx::query_as::<_, ListableResource>(&sql)
@@ -642,14 +655,15 @@ async fn create_resource(
sqlx::query!(
"INSERT INTO resource
(workspace_id, path, value, description, resource_type)
VALUES ($1, $2, $3, $4, $5) ON CONFLICT (workspace_id, path)
DO UPDATE SET value = $3, description = $4, resource_type = $5",
(workspace_id, path, value, description, resource_type, created_by, edited_at)
VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)
DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()",
w_id,
resource.path,
raw_json as sqlx::types::Json<&RawValue>,
resource.description,
resource.resource_type,
authed.username
)
.execute(&mut *tx)
.await?;
@@ -772,6 +786,8 @@ async fn update_resource(
sqlb.set_str("description", ndesc);
}
sqlb.set_str("edited_at", "now()");
sqlb.returning("path");
let authed = maybe_refresh_folders(path, &w_id, authed, &db).await;
@@ -853,7 +869,7 @@ async fn update_resource_value(
let mut tx = user_db.begin(&authed).await?;
sqlx::query!(
"UPDATE resource SET value = $1 WHERE path = $2 AND workspace_id = $3",
"UPDATE resource SET value = $1, edited_at = now() WHERE path = $2 AND workspace_id = $3",
nv.value,
path,
w_id
@@ -979,12 +995,13 @@ async fn create_resource_type(
sqlx::query!(
"INSERT INTO resource_type
(workspace_id, name, schema, description)
VALUES ($1, $2, $3, $4)",
(workspace_id, name, schema, description, created_by, edited_at)
VALUES ($1, $2, $3, $4, $5, now())",
w_id,
resource_type.name,
resource_type.schema,
resource_type.description,
authed.username
)
.execute(&mut *tx)
.await?;
@@ -1120,6 +1137,7 @@ async fn update_resource_type(
if let Some(ndesc) = ns.description {
sqlb.set_str("description", ndesc);
}
sqlb.set_str("edited_at", "now()");
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
let mut tx = user_db.begin(&authed).await?;
+4
View File
@@ -313,6 +313,7 @@ pub struct ListScheduleQuery {
pub path: Option<String>,
pub is_flow: Option<bool>,
pub args: Option<String>,
pub path_start: Option<String>,
}
async fn list_schedule(
@@ -339,6 +340,9 @@ async fn list_schedule(
if let Some(args) = &lsq.args {
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
}
if let Some(path_start) = &lsq.path_start {
sqlb.and_where_like_left("path", path_start);
}
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
let rows = sqlx::query_as::<_, Schedule>(&sql)
.fetch_all(&mut *tx)
+2 -2
View File
@@ -242,10 +242,10 @@ async fn list_scripts(
sqlb.and_where_eq("archived", false);
}
if let Some(ps) = &lq.path_start {
sqlb.and_where_like_left("path", "?".bind(ps));
sqlb.and_where_like_left("o.path", ps);
}
if let Some(p) = &lq.path_exact {
sqlb.and_where_eq("path", "?".bind(p));
sqlb.and_where_eq("o.path", "?".bind(p));
}
if let Some(cb) = &lq.created_by {
sqlb.and_where_eq("created_by", "?".bind(cb));
+11 -2
View File
@@ -81,10 +81,16 @@ async fn list_contextual_variables(
))
}
#[derive(Deserialize)]
struct ListVariableQuery {
path_start: Option<String>,
}
async fn list_variables(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Query(lq): Query<ListVariableQuery>,
) -> JsonResult<Vec<ListableVariable>> {
let mut tx = user_db.begin(&authed).await?;
@@ -97,10 +103,13 @@ async fn list_variables(
from variable
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $1
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = $1
WHERE variable.workspace_id = $1 AND variable.path NOT LIKE 'u/' || $2 || '/secret_arg/%' ORDER BY path",
WHERE variable.workspace_id = $1 AND variable.path NOT LIKE 'u/' || $2 || '/secret_arg/%'
AND variable.path LIKE $3 || '%'
ORDER BY path",
)
.bind(&w_id)
.bind(&authed.username)
.bind(&lq.path_start.unwrap_or_default())
.fetch_all(&mut *tx)
.await?;
@@ -523,7 +532,7 @@ async fn update_variable(
}
sqlx::query!(
"UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4",
"UPDATE resource SET path = $1, value = $2, edited_at = now() WHERE path = $3 AND workspace_id = $4",
npath,
v,
path,
+8 -4
View File
@@ -1605,29 +1605,33 @@ async fn create_workspace(
.await?;
sqlx::query!(
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
nw.id,
username,
)
.execute(&mut *tx)
.await?;
sqlx::query!(
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
nw.id,
username,
)
.execute(&mut *tx)
.await?;
sqlx::query!(
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
nw.id,
username,
)
.execute(&mut *tx)
.await?;
sqlx::query!(
"INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING",
"INSERT INTO resource (workspace_id, path, value, description, resource_type, created_by, edited_at) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme', $2, now()) ON CONFLICT DO NOTHING",
nw.id,
username,
)
.execute(&mut *tx)
.await?;
+2
View File
@@ -11,4 +11,6 @@ use serde::Deserialize;
#[derive(Deserialize)]
pub struct ListAppQuery {
pub starred_only: Option<bool>,
pub path_exact: Option<String>,
pub path_start: Option<String>,
}
+5 -4
View File
@@ -1568,13 +1568,14 @@ pub async fn save_in_cache(
if let Err(e) = sqlx::query!(
"INSERT INTO resource
(workspace_id, path, value, resource_type)
VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path)
DO UPDATE SET value = $3",
(workspace_id, path, value, resource_type, created_by, edited_at)
VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)
DO UPDATE SET value = $3, edited_at = now()",
job.workspace_id,
cached_path,
raw_json as sqlx::types::Json<CachedResource>,
"cache"
"cache",
job.created_by
)
.execute(db)
.await
-12
View File
@@ -94,18 +94,6 @@
.Template-editor span.mtk20 {
color: black !important;
}
::-webkit-scrollbar {
width: 9px;
height: 9px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(155, 155, 155, 0.5);
border: transparent;
}
}
@layer components {
@@ -16,6 +16,9 @@
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import Section from './Section.svelte'
import { Eye, Plus } from 'lucide-svelte'
import Label from './Label.svelte'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher } from 'svelte'
export let name: string
let can_write = false
@@ -67,6 +70,7 @@
async function loadFolder(): Promise<void> {
try {
folder = await FolderService.getFolder({ workspace: $workspaceStore!, name })
summary = folder.summary ?? ''
can_write =
$userStore != undefined &&
(folder?.owners.includes('u/' + $userStore.username) ||
@@ -108,6 +112,7 @@
let ownerKind: 'user' | 'group' = 'user'
let groupCreated: string | undefined = undefined
let newGroupName: string = ''
let summary: string = ''
async function addGroup() {
await GroupService.createGroup({
@@ -119,6 +124,19 @@
loadGroups()
ownerItem = newGroupName
}
const dispatch = createEventDispatcher()
async function updateFolder() {
await FolderService.updateFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: { summary }
})
sendUserToast('Folder summary updated')
dispatch('update')
loadFolder()
}
</script>
<Drawer bind:this={newGroup}>
@@ -148,6 +166,15 @@
</DrawerContent>
</Drawer>
<Section label="Metadata" class="mb-4">
<Label label="Summary">
<div class="flex flex-row gap-2">
<input placeholder="Short summary to be displayed when listed" bind:value={summary} />
<Button size="sm" on:click={updateFolder} disabled={!can_write}>Save</Button>
</div>
</Label>
</Section>
<Section label={`Permissions (${perms?.length ?? 0})`}>
<div class="flex flex-col gap-6">
{#if can_write}
+36 -91
View File
@@ -9,7 +9,6 @@
import { userStore, workspaceStore } from '$lib/stores'
import AutoComplete from 'simple-svelte-autocomplete'
import { createEventDispatcher } from 'svelte'
import autosize from '$lib/autosize'
import { Button } from './common'
import Skeleton from './common/skeleton/Skeleton.svelte'
import TableCustom from './TableCustom.svelte'
@@ -17,6 +16,8 @@
import { canWrite } from '$lib/utils'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import Section from './Section.svelte'
import Label from './Label.svelte'
export let name: string
let can_write = false
@@ -27,6 +28,7 @@
let members: { member_name: string; role: Role }[] | undefined = undefined
let usernames: string[] | undefined = []
let username: string = ''
let summary = ''
const dispatch = createEventDispatcher()
@@ -78,9 +80,11 @@
role: getRole(x)
}
})
summary = group.summary ?? ''
} catch (e) {
can_write = false
members = []
summary = ''
group = {
name
}
@@ -101,37 +105,28 @@
}
</script>
<div class="flex flex-col gap-6">
<h1>{name}</h1>
{#if group}
<div class="flex flex-col gap-1">
<textarea
disabled={!can_write}
rows="2"
use:autosize
bind:value={group.summary}
placeholder="Summary of the group"
/>
<div class="flex justify-end">
<Button
disabled={!can_write}
size="xs"
on:click={async () => {
await GroupService.updateGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: { summary: group?.summary }
})
dispatch('update')
sendUserToast('New summary saved')
}}>Save Summary</Button
>
</div>
<Section label="Metadata" class="mb-4">
<Label label="Summary">
<div class="flex flex-row gap-2">
<input placeholder="Short summary to be displayed when listed" bind:value={summary} />
<Button
size="sm"
on:click={async () => {
await GroupService.updateGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: { summary }
})
dispatch('update')
sendUserToast('Group summary updated')
loadGroup()
}}>Save</Button
>
</div>
{:else}
<Skeleton layout={[[4]]} />
{/if}
<h2>Members ({members?.length ?? 0})</h2>
</Label>
</Section>
<Section label={`Members (${members?.length ?? 0})`}>
{#if can_write}
<div class="flex items-start">
<AutoComplete required noInputStyles items={usernames} bind:selectedItem={username} />
@@ -221,13 +216,6 @@
label="Member"
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
/>
<!-- <ToggleButton position="center" value="manager" size="xs"
>Manager <Tooltip
>A manager of a group can manage the group, adding and removing users and
change their roles. Being a manager does not make you a member.</Tooltip
></ToggleButton
> -->
<ToggleButton
position="right"
value="admin"
@@ -235,6 +223,15 @@
label="Admin"
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
/>
{#if role === 'manager'}
<ToggleButton
value="manager"
size="xs"
label="Manager"
tooltip="A manager of a group can manage the group, adding and removing users and
change their roles. Being a manager does not make you a member"
/>
{/if}
</ToggleButtonGroup>
</div>
{:else}
@@ -281,58 +278,6 @@
</tbody>
</TableCustom>
{/if}
<!-- <h2 class="mt-10"
>Groups managing this group <Tooltip>Any member of those groups can manage this group</Tooltip
></h2
>
{#if can_write}
<div class="flex items-start">
<AutoComplete items={groups} bind:selectedItem={new_managing_group} />
<Button
variant="contained"
color="blue"
size="sm"
btnClasses="!ml-4"
on:click={addToManagingGroup}
>
Add group managing this group
</Button>
</div>
{/if}
{#if managing_groups.length == 0}
<p class="text-tertiary text-sm">No group is managing this group</p>
{:else}
<TableCustom>
<tr slot="header-row">
<th>group</th>
<th />
</tr>
<tbody slot="body">
{#each managing_groups as managing_group}<tr>
<td>{managing_group.split('/')[1]}</td>
<td>
{#if can_write}
<button
class="ml-2 text-red-500"
on:click={async () => {
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: managing_group
}
})
loadGroup()
}}>remove</button
>
{/if}</td
>
</tr>{/each}
</tbody>
</TableCustom>
{/if} -->
{:else}
<div class="flex flex-col">
{#each new Array(6) as _}
@@ -340,4 +285,4 @@
{/each}
</div>
{/if}
</div>
</Section>
+1 -1
View File
@@ -17,7 +17,7 @@
{#if members}
<Popover
><div class="inline-flex gap-1 items-end"
><span class="text-tertiary text-xs mb-0.5">({members.length})</span>
><span class="text-tertiary text-xs">({members.length})</span>
<div class="max-w-xs truncate"
><span class="text-tertiary text-xs">{members?.join(', ')}</span></div
></div
+2 -2
View File
@@ -279,8 +279,8 @@
}}
>
{#if !folderCreated}
<div class="flex flex-row">
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
<div class="flex flex-col gap-2">
<input placeholder="New folder name" bind:value={newFolderName} />
<Button size="md" startIcon={{ icon: Plus }} disabled={!newFolderName} on:click={addFolder}>
New&nbsp;folder
</Button>
@@ -27,6 +27,7 @@
import { List, Loader2, Save } from 'lucide-svelte'
import FlowRetries from './flows/content/FlowRetries.svelte'
import WorkerTagPicker from './WorkerTagPicker.svelte'
import Label from './Label.svelte'
let optionTabSelected: 'error_handler' | 'recovery_handler' | 'retries' = 'error_handler'
@@ -441,16 +442,16 @@
</svelte:fragment>
<div class="flex flex-col gap-12">
<div>
<div class="flex flex-col gap-4">
<div>
<h2 class="text-base font-semibold">Metadata</h2>
<div class="w-full py-2">
<h2 class="text-base font-semibold mb-2">Metadata</h2>
<Label label="Summary">
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
placeholder="Schedule summary"
class="text-sm w-full font-semibold"
placeholder="Short summary to be displayed when listed"
class="text-sm w-full"
bind:value={summary}
on:keyup={() => {
if (!edit && summary?.length > 0 && !dirtyPath) {
@@ -464,40 +465,42 @@
}
}}
/>
</div>
</Label>
</div>
{#if !edit}
<Path
bind:dirty={dirtyPath}
bind:this={pathC}
checkInitialPathExistence
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder="schedule"
kind="schedule"
/>
{:else}
<div class="flex justify-start w-full">
<Badge
color="gray"
class="center-center !bg-surface-secondary !text-tertiary !h-[24px] rounded-r-none border"
>
Schedule path (not editable)
</Badge>
<input
type="text"
readonly
value={path}
size={path?.length || 50}
class="font-mono !text-xs grow shrink overflow-x-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
}}
<Label label="Path">
{#if !edit}
<Path
bind:dirty={dirtyPath}
bind:this={pathC}
checkInitialPathExistence
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder="schedule"
kind="schedule"
/>
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
</div>
{/if}
{:else}
<div class="flex justify-start w-full">
<Badge
color="gray"
class="center-center !bg-surface-secondary !text-tertiary !h-[24px] rounded-r-none border"
>
Schedule path (not editable)
</Badge>
<input
type="text"
readonly
value={path}
size={path?.length || 50}
class="font-mono !text-xs grow shrink overflow-x-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
}}
/>
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
</div>
{/if}
</Label>
</div>
<Section label="Schedule">
@@ -10,6 +10,7 @@
import { Calendar } from 'lucide-svelte'
import type { Writable } from 'svelte/store'
import type { ScriptSchedule } from '$lib/scripts'
import Label from './Label.svelte'
export let initialPath: string
export let schema: Record<string, any> | undefined
@@ -37,15 +38,17 @@
$: initialPath && loadSchedules()
</script>
<div class="w-full py-2">
<div class="w-full flex flex-col gap-4 mb-4">
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
placeholder="Schedule summary"
class="text-sm w-full font-semibold mb-4"
bind:value={$schedule.summary}
/>
<Label label="Summary">
<input
autofocus
type="text"
placeholder="Short summary to be displayed when listed"
class="text-sm w-full"
bind:value={$schedule.summary}
/>
</Label>
</div>
<CronInput bind:schedule={$schedule.cron} bind:timezone={$schedule.timezone} />
@@ -10,6 +10,7 @@
import { ScheduleService, type Schedule } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Calendar } from 'lucide-svelte'
import Label from '$lib/components/Label.svelte'
const { schedule, flowStore, initialPath } = getContext<FlowEditorContext>('FlowEditorContext')
let schedules: Schedule[] | undefined = undefined
@@ -33,15 +34,17 @@
$: initialPath && loadSchedules()
</script>
<div class="w-full py-2">
<div class="w-full flex flex-col gap-4 mb-4">
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
placeholder="Schedule summary"
class="text-sm w-full font-semibold mb-4"
bind:value={$schedule.summary}
/>
<Label label="Summary">
<input
autofocus
type="text"
placeholder="Short summary to be displayed when listed"
class="text-sm w-full"
bind:value={$schedule.summary}
/>
</Label>
</div>
<CronInput bind:schedule={$schedule.cron} bind:timezone={$schedule.timezone} />
@@ -150,7 +150,7 @@
</tr>
{/if}
{#each folders as { name, extra_perms, owners, canWrite } (name)}
{#each folders as { name, extra_perms, owners, canWrite, summary } (name)}
<Row
hoverable
on:click={() => {
@@ -160,6 +160,10 @@
>
<Cell first>
<span class="text-blue-500">{name}</span>
{#if summary}
<br />
<span class="text-gray-500">{summary}</span>
{/if}
</Cell>
<FolderUsageInfo {name} tabular />
@@ -14,6 +14,10 @@
import { userStore, workspaceStore } from '$lib/stores'
import { canWrite } from '$lib/utils'
import { Pen, Plus, Trash } from 'lucide-svelte'
import DataTable from '$lib/components/table/DataTable.svelte'
import Head from '$lib/components/table/Head.svelte'
import Cell from '$lib/components/table/Cell.svelte'
import Row from '$lib/components/table/Row.svelte'
type GroupW = Group & { canWrite: boolean }
@@ -110,15 +114,15 @@
</PageHeader>
<div class="relative mb-20 pt-8">
<TableCustom>
<tr slot="header-row">
<th class="!px-0" />
<th>Name</th>
<th>Summary</th>
<th>Members</th>
<th />
</tr>
<tbody slot="body">
<DataTable>
<Head>
<tr>
<Cell head first>Name</Cell>
<Cell head>Members</Cell>
<Cell head last />
</tr>
</Head>
<tbody class="divide-y">
{#if groups === undefined}
{#each new Array(4) as _}
<tr>
@@ -129,23 +133,29 @@
{/each}
{:else}
{#each groups as { name, summary, extra_perms, canWrite } (name)}
<tr>
<td class="!px-0 text-center">
<SharedBadge {canWrite} extraPerms={extra_perms} />
</td>
<td>
<a
href="#{name}"
on:click={() => {
editGroupName = name
groupDrawer.openDrawer()
}}
>{name}
</a>
</td>
<td>{summary ?? ''}</td>
<td><GroupInfo {name} /></td>
<td>
<Row
hoverable
on:click={() => {
editGroupName = name
groupDrawer.openDrawer()
}}
>
<Cell first>
<div class="flex flex-row gap-2 justify-between">
<div>
<span class="text-blue-500">{name}</span>
{#if summary}
<br />
<span class="text-gray-500">{summary}</span>
{/if}
</div>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</div>
</Cell>
<Cell>
<GroupInfo {name} />
</Cell>
<Cell>
<Dropdown
items={[
{
@@ -170,12 +180,12 @@
}
]}
/>
</td>
</tr>
</Cell>
</Row>
{/each}
{/if}
</tbody>
</TableCustom>
</DataTable>
</div>
{#if instanceGroups && instanceGroups.length > 0}
@@ -122,6 +122,7 @@
let preFilteredItems: typeof filteredItems | undefined = []
let filter = ''
let ownerFilter: string | undefined = undefined
let nbDisplayed = 15
let filterEnabledDisabled: 'all' | 'enabled' | 'disabled' = 'all'
@@ -290,7 +291,7 @@
<div class="text-center text-sm text-tertiary mt-2"> No schedules </div>
{:else if items?.length}
<div class="border rounded-md divide-y">
{#each items as { path, error, summary, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, extra_perms, canWrite, args, marked, jobs } (path)}
{#each items.slice(0, nbDisplayed) as { path, error, summary, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, extra_perms, canWrite, args, marked, jobs } (path)}
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
{@const avg_s = jobs
? jobs.reduce((acc, x) => acc + x.duration_ms, 0) / jobs.length
@@ -478,6 +479,12 @@
<NoItemFound />
{/if}
</div>
{#if items && items?.length > 15 && nbDisplayed < items.length}
<span class="text-xs"
>{nbDisplayed} items out of {items.length}
<button class="ml-4" on:click={() => (nbDisplayed += 30)}>load 30 more</button></span
>
{/if}
</CenteredPage>
<ShareModal