feat: git sync now accepts path filters and type filters (#3189)
* feat: git sync now accepts path filters and type filters * fix git sync for folders * UI nits * Add folder by default in migration * fix openapi
This commit is contained in:
committed by
GitHub
parent
2a7c6667e2
commit
126773122a
1
backend/Cargo.lock
generated
1
backend/Cargo.lock
generated
@@ -9293,6 +9293,7 @@ dependencies = [
|
||||
name = "windmill-git-sync"
|
||||
version = "1.265.3"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"rsmq_async",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Add up migration script here
|
||||
UPDATE workspace_settings SET git_sync = (
|
||||
CASE
|
||||
WHEN git_sync is null THEN null
|
||||
WHEN git_sync = '[]'::jsonb THEN null
|
||||
ELSE jsonb_build_object(
|
||||
'repositories', git_sync,
|
||||
'include_path', '["f/**"]'::jsonb,
|
||||
'include_type', '["script", "flow", "app", "folder"]'::jsonb
|
||||
|
||||
)
|
||||
END
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1195,9 +1195,7 @@ paths:
|
||||
large_file_storage:
|
||||
$ref: "#/components/schemas/LargeFileStorage"
|
||||
git_sync:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkspaceGitSync"
|
||||
$ref: "#/components/schemas/WorkspaceGitSyncSettings"
|
||||
default_app:
|
||||
type: string
|
||||
required:
|
||||
@@ -1540,9 +1538,7 @@ paths:
|
||||
type: object
|
||||
properties:
|
||||
git_sync_settings:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkspaceGitSync"
|
||||
$ref: "#/components/schemas/WorkspaceGitSyncSettings"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
@@ -9508,7 +9504,28 @@ components:
|
||||
- useSSL
|
||||
- pathStyle
|
||||
|
||||
WorkspaceGitSync:
|
||||
WorkspaceGitSyncSettings:
|
||||
type: object
|
||||
properties:
|
||||
include_path:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
include_type:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- script
|
||||
- flow
|
||||
- app
|
||||
- folder
|
||||
repositories:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/GitRepositorySettings"
|
||||
|
||||
GitRepositorySettings:
|
||||
type: object
|
||||
properties:
|
||||
script_path:
|
||||
|
||||
@@ -212,7 +212,7 @@ async fn create_folder(
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
DeployedObject::Folder { path: format!("f/{}/folder.meta.*", ng.name) },
|
||||
DeployedObject::Folder { path: format!("f/{}", ng.name) },
|
||||
Some(format!("Folder '{}' created", ng.name)),
|
||||
rsmq,
|
||||
true,
|
||||
@@ -500,7 +500,7 @@ async fn delete_folder(
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
DeployedObject::Folder { path: format!("f/{}/folder.meta.*", name) },
|
||||
DeployedObject::Folder { path: format!("f/{}", name) },
|
||||
Some(format!("Folder '{}' deleted", name)),
|
||||
rsmq,
|
||||
true,
|
||||
|
||||
@@ -88,7 +88,7 @@ async fn add_granular_acl(
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
DeployedObject::Folder { path: format!("f/{}/folder.meta.*", path) },
|
||||
DeployedObject::Folder { path: format!("f/{}", path) },
|
||||
Some(format!("Folder '{}' changed permissions", path)),
|
||||
rsmq,
|
||||
true,
|
||||
@@ -201,7 +201,7 @@ async fn remove_granular_acl(
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
DeployedObject::Folder { path: format!("f/{}/folder.meta.*", path) },
|
||||
DeployedObject::Folder { path: format!("f/{}", path) },
|
||||
Some(format!("Folder '{}' changed permissions", path)),
|
||||
rsmq,
|
||||
true,
|
||||
|
||||
@@ -45,7 +45,7 @@ use windmill_common::schedule::Schedule;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::variables::build_crypt;
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::workspaces::WorkspaceGitRepo;
|
||||
use windmill_common::workspaces::WorkspaceGitSyncSettings;
|
||||
use windmill_common::{
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
flows::Flow,
|
||||
@@ -165,7 +165,7 @@ pub struct WorkspaceSettings {
|
||||
pub error_handler_extra_args: Option<serde_json::Value>,
|
||||
pub error_handler_muted_on_cancel: Option<bool>,
|
||||
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
|
||||
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitRepo
|
||||
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
|
||||
pub default_app: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1143,7 +1143,7 @@ async fn edit_large_file_storage_config(
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditGitSyncConfig {
|
||||
git_sync_settings: Option<Vec<WorkspaceGitRepo>>,
|
||||
git_sync_settings: Option<WorkspaceGitSyncSettings>,
|
||||
}
|
||||
|
||||
async fn edit_git_sync_config(
|
||||
@@ -1177,7 +1177,7 @@ async fn edit_git_sync_config(
|
||||
.await?;
|
||||
|
||||
if let Some(git_sync_settings) = new_config.git_sync_settings {
|
||||
let serialized_config = serde_json::to_value::<Vec<WorkspaceGitRepo>>(git_sync_settings)
|
||||
let serialized_config = serde_json::to_value::<WorkspaceGitSyncSettings>(git_sync_settings)
|
||||
.map_err(|err| Error::InternalErr(err.to_string()))?;
|
||||
|
||||
sqlx::query!(
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
pub struct WorkspaceGitSyncSettings {
|
||||
pub include_path: Vec<String>,
|
||||
pub include_type: Vec<ObjectType>,
|
||||
pub repositories: Vec<GitRepositorySettings>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
pub enum ObjectType {
|
||||
Script,
|
||||
Flow,
|
||||
App,
|
||||
Folder,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WorkspaceGitRepo {
|
||||
pub struct GitRepositorySettings {
|
||||
pub script_path: String,
|
||||
pub git_repo_resource_path: String,
|
||||
pub use_individual_branch: Option<bool>,
|
||||
|
||||
@@ -20,4 +20,5 @@ serde_json.workspace = true
|
||||
tracing.workspace = true
|
||||
windmill-common = { workspace = true, features = ["axum"] }
|
||||
windmill-queue.workspace = true
|
||||
rsmq_async.workspace = true
|
||||
rsmq_async.workspace = true
|
||||
regex = "1.10.3"
|
||||
@@ -11,8 +11,9 @@ use std::collections::HashMap;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use uuid::Uuid;
|
||||
use windmill_common::users::SUPERADMIN_SYNC_EMAIL;
|
||||
use windmill_common::workspaces::WorkspaceGitRepo;
|
||||
use windmill_common::workspaces::{ObjectType, WorkspaceGitSyncSettings};
|
||||
|
||||
use regex::Regex;
|
||||
use serde_json::json;
|
||||
use windmill_common::error::{Error, Result};
|
||||
use windmill_common::jobs::JobPayload;
|
||||
@@ -59,23 +60,44 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
rsmq: Option<R>,
|
||||
skip_db_insert: bool,
|
||||
) -> Result<()> {
|
||||
let exclude_path_prefix = "u/";
|
||||
let obj_path = if obj.get_path().starts_with(exclude_path_prefix) {
|
||||
None
|
||||
} else {
|
||||
let workspace_git_repo_setting = sqlx::query_scalar!(
|
||||
"SELECT git_sync FROM workspace_settings WHERE workspace_id = $1",
|
||||
w_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
Error::BadRequest(format!(
|
||||
"No workspace settings found for workspace ID: {} - Error: {}",
|
||||
w_id,
|
||||
err.to_string()
|
||||
))
|
||||
})?;
|
||||
|
||||
let workspace_git_sync_settings = workspace_git_repo_setting
|
||||
.map(|conf| serde_json::from_value::<WorkspaceGitSyncSettings>(conf).ok())
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let obj_path = if one_regexp_match(
|
||||
obj.get_path(),
|
||||
workspace_git_sync_settings.include_path.clone(),
|
||||
) {
|
||||
Some(obj.get_path())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let obj_parent_path = if obj
|
||||
.get_parent_path()
|
||||
.unwrap_or(exclude_path_prefix.to_string())
|
||||
.starts_with(exclude_path_prefix)
|
||||
.map(|p| one_regexp_match(p.as_str(), workspace_git_sync_settings.include_path))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
obj.get_parent_path()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let skip_git_sync = if obj_path.is_none() && obj_parent_path.is_none() {
|
||||
let mut skip_git_sync = if obj_path.is_none() && obj_parent_path.is_none() {
|
||||
tracing::debug!(
|
||||
"Ignoring {} from git sync as it's in a private user folder",
|
||||
obj.get_path()
|
||||
@@ -85,28 +107,31 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
false
|
||||
};
|
||||
|
||||
let workspace_git_repo_setting = sqlx::query_scalar!(
|
||||
"SELECT git_sync FROM workspace_settings WHERE workspace_id = $1",
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
skip_git_sync = skip_git_sync
|
||||
|| match obj {
|
||||
DeployedObject::Script { .. } => !workspace_git_sync_settings
|
||||
.include_type
|
||||
.iter()
|
||||
.any(|element| *element == ObjectType::Script),
|
||||
DeployedObject::Flow { .. } => !workspace_git_sync_settings
|
||||
.include_type
|
||||
.iter()
|
||||
.any(|element| *element == ObjectType::Flow),
|
||||
DeployedObject::App { .. } => !workspace_git_sync_settings
|
||||
.include_type
|
||||
.iter()
|
||||
.any(|element| *element == ObjectType::App),
|
||||
DeployedObject::Folder { .. } => !workspace_git_sync_settings
|
||||
.include_type
|
||||
.iter()
|
||||
.any(|element| *element == ObjectType::Folder),
|
||||
};
|
||||
|
||||
if workspace_git_repo_setting.is_none() {
|
||||
return Err(Error::InternalErr(
|
||||
"No workspace settings found for workspace ID".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let workspace_git_repos = workspace_git_repo_setting
|
||||
.unwrap()
|
||||
.map(|conf| serde_json::from_value::<Vec<WorkspaceGitRepo>>(conf).ok())
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
tracing::debug!("Skipping git sync for {:?} -> {}", obj_path, skip_git_sync);
|
||||
|
||||
let mut git_sync_job_uuids: Vec<Uuid> = vec![];
|
||||
if !skip_git_sync {
|
||||
for workspace_git_repo in workspace_git_repos {
|
||||
for workspace_git_repo in workspace_git_sync_settings.repositories {
|
||||
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
|
||||
args.insert(
|
||||
"repo_url_resource_path".to_string(),
|
||||
@@ -236,3 +261,43 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn one_regexp_match(path: &str, regexps: Vec<String>) -> bool {
|
||||
let has_match = regexps
|
||||
.iter()
|
||||
.map(|regexp| transform_regexp(regexp.to_owned()))
|
||||
.filter(|regexp| regexp.is_some())
|
||||
.map(|regexp| regexp.unwrap())
|
||||
.any(|regexp| regexp.is_match(path));
|
||||
tracing::debug!(
|
||||
"{} matches the set of regexps {:?} -> {}",
|
||||
path,
|
||||
regexps,
|
||||
has_match
|
||||
);
|
||||
return has_match;
|
||||
}
|
||||
|
||||
fn transform_regexp(user_regexp: String) -> Option<Regex> {
|
||||
// this is annoying b/c we want to replace ** with [a-zA-Z0-9_.*/]* AND THEN * with [a-zA-Z0-9_.*]* - but b/c the
|
||||
// first replacement string contains itself '*', we can't do it naively. So, the hack is to use a different
|
||||
// character in the replacement string, instead of '*' we use here '%', and finally we replace all '%' with '*'
|
||||
let mut regexp = user_regexp
|
||||
.replace("**", "[a-zA-Z0-9_.%/]%")
|
||||
.replace("*", "[a-zA-Z0-9_.%]%");
|
||||
regexp = regexp.replace("%", "*");
|
||||
// Then we add ^ at the beginning and '$' at the end to match the entire string, not just a substring
|
||||
regexp = if regexp.starts_with("^") {
|
||||
regexp
|
||||
} else {
|
||||
format!("^{}", regexp)
|
||||
};
|
||||
regexp = if regexp.ends_with("$") {
|
||||
regexp
|
||||
} else {
|
||||
format!("{}$", regexp)
|
||||
};
|
||||
let compiled_regexp = Regex::new(regexp.as_str()).ok();
|
||||
tracing::debug!("Compiled regexp: {:?}", compiled_regexp);
|
||||
return compiled_regexp;
|
||||
}
|
||||
|
||||
@@ -33,13 +33,14 @@
|
||||
} from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { setQueryWithoutLoad, emptyString, tryEvery } from '$lib/utils'
|
||||
import { Scroll, Slack, XCircle, RotateCw, CheckCircle2 } from 'lucide-svelte'
|
||||
import { Scroll, Slack, XCircle, RotateCw, CheckCircle2, X, Plus } from 'lucide-svelte'
|
||||
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
|
||||
|
||||
import PremiumInfo from '$lib/components/settings/PremiumInfo.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import TestOpenaiKey from '$lib/components/copilot/TestOpenaiKey.svelte'
|
||||
import Portal from 'svelte-portal'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
let s3FileViewer: S3FilePicker
|
||||
|
||||
@@ -64,10 +65,19 @@
|
||||
publicResource: boolean | undefined
|
||||
}
|
||||
let gitSyncSettings: {
|
||||
script_path: string
|
||||
git_repo_resource_path: string
|
||||
use_individual_branch: boolean
|
||||
}[]
|
||||
include_path: string[]
|
||||
repositories: {
|
||||
script_path: string
|
||||
git_repo_resource_path: string
|
||||
use_individual_branch: boolean
|
||||
}[]
|
||||
include_type: {
|
||||
scripts: boolean
|
||||
flows: boolean
|
||||
apps: boolean
|
||||
folders: boolean
|
||||
}
|
||||
}
|
||||
let gitSyncTestJobs: {
|
||||
jobId: string | undefined
|
||||
status: 'running' | 'success' | 'failure' | undefined
|
||||
@@ -208,7 +218,7 @@
|
||||
|
||||
async function editWindmillGitSyncSettings(): Promise<void> {
|
||||
let alreadySeenResource: string[] = []
|
||||
let finalSettings = gitSyncSettings.map((elmt) => {
|
||||
let repositories = gitSyncSettings.repositories.map((elmt) => {
|
||||
alreadySeenResource.push(elmt.git_repo_resource_path)
|
||||
return {
|
||||
script_path: elmt.script_path,
|
||||
@@ -216,15 +226,38 @@
|
||||
use_individual_branch: elmt.use_individual_branch
|
||||
}
|
||||
})
|
||||
|
||||
let include_path = gitSyncSettings.include_path.filter((elmt) => {
|
||||
return !emptyString(elmt)
|
||||
})
|
||||
|
||||
let include_type: ('script' | 'flow' | 'app' | 'folder')[] = []
|
||||
if (gitSyncSettings.include_type.scripts) {
|
||||
include_type.push('script')
|
||||
}
|
||||
if (gitSyncSettings.include_type.flows) {
|
||||
include_type.push('flow')
|
||||
}
|
||||
if (gitSyncSettings.include_type.apps) {
|
||||
include_type.push('app')
|
||||
}
|
||||
if (gitSyncSettings.include_type.folders) {
|
||||
include_type.push('folder')
|
||||
}
|
||||
|
||||
if (alreadySeenResource.some((res, index) => alreadySeenResource.indexOf(res) !== index)) {
|
||||
sendUserToast('Same Git resource used more than once', true)
|
||||
return
|
||||
}
|
||||
if (finalSettings.length > 0) {
|
||||
if (repositories.length > 0 || include_path.length > 1 || include_path[0] !== 'f/**') {
|
||||
await WorkspaceService.editWorkspaceGitSyncConfig({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
git_sync_settings: finalSettings
|
||||
git_sync_settings: {
|
||||
repositories: repositories,
|
||||
include_path: include_path,
|
||||
include_type: include_type
|
||||
}
|
||||
}
|
||||
})
|
||||
sendUserToast('Workspace Git sync settings updated')
|
||||
@@ -310,28 +343,45 @@
|
||||
publicResource: undefined
|
||||
}
|
||||
}
|
||||
if (
|
||||
settings.git_sync !== undefined &&
|
||||
settings.git_sync !== null &&
|
||||
settings.git_sync.length > 0
|
||||
) {
|
||||
gitSyncSettings = settings.git_sync.map((settings) => {
|
||||
return {
|
||||
git_repo_resource_path: settings.git_repo_resource_path.replace('$res:', ''),
|
||||
script_path: settings.script_path,
|
||||
use_individual_branch: settings.use_individual_branch ?? false
|
||||
if (settings.git_sync !== undefined && settings.git_sync !== null) {
|
||||
gitSyncTestJobs = []
|
||||
gitSyncSettings = {
|
||||
include_path:
|
||||
settings.git_sync.include_path?.length ?? 0 > 0
|
||||
? settings.git_sync.include_path ?? []
|
||||
: ['f/**'],
|
||||
repositories: (settings.git_sync.repositories ?? []).map((settings) => {
|
||||
gitSyncTestJobs.push({
|
||||
jobId: undefined,
|
||||
status: undefined
|
||||
})
|
||||
return {
|
||||
git_repo_resource_path: settings.git_repo_resource_path.replace('$res:', ''),
|
||||
script_path: settings.script_path,
|
||||
use_individual_branch: settings.use_individual_branch ?? false
|
||||
}
|
||||
}),
|
||||
include_type: {
|
||||
scripts: (settings.git_sync.include_type?.indexOf('script') ?? -1) >= 0,
|
||||
flows: (settings.git_sync.include_type?.indexOf('flow') ?? -1) >= 0,
|
||||
apps: (settings.git_sync.include_type?.indexOf('app') ?? -1) >= 0,
|
||||
folders: (settings.git_sync.include_type?.indexOf('folder') ?? -1) >= 0
|
||||
}
|
||||
})
|
||||
gitSyncTestJobs = settings.git_sync.map((settings) => {
|
||||
return {
|
||||
jobId: undefined,
|
||||
status: undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
gitSyncSettings = []
|
||||
gitSyncSettings = {
|
||||
include_path: ['f/**'],
|
||||
repositories: [],
|
||||
include_type: {
|
||||
scripts: true,
|
||||
flows: true,
|
||||
apps: true,
|
||||
folders: true
|
||||
}
|
||||
}
|
||||
gitSyncTestJobs = []
|
||||
}
|
||||
console.log(gitSyncSettings)
|
||||
|
||||
// check openai_client_credentials_oauth
|
||||
usingOpenaiClientCredentialsOauth = await ResourceService.existsResourceType({
|
||||
@@ -381,15 +431,15 @@
|
||||
}
|
||||
|
||||
async function runGitSyncTestJob(settingsIdx: number) {
|
||||
let gitSyncSettingsElmt = gitSyncSettings[settingsIdx]
|
||||
if (emptyString(gitSyncSettingsElmt.script_path)) {
|
||||
let gitSyncRepository = gitSyncSettings.repositories[settingsIdx]
|
||||
if (emptyString(gitSyncRepository.script_path)) {
|
||||
return
|
||||
}
|
||||
let jobId = await JobService.runScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: 'hub/7925/git-repo-test-read-write-windmill',
|
||||
requestBody: {
|
||||
repo_url_resource_path: gitSyncSettingsElmt.git_repo_resource_path.replace('$res:', '')
|
||||
repo_url_resource_path: gitSyncRepository.git_repo_resource_path.replace('$res:', '')
|
||||
}
|
||||
})
|
||||
gitSyncTestJobs[settingsIdx] = {
|
||||
@@ -852,28 +902,19 @@
|
||||
scripts, flows and apps to the repository on each deploy.
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
{#if !$enterpriseLicense}
|
||||
<Alert type="warning" title="Syncing workspace to Git is an EE feature">
|
||||
Automatically saving scripts to a Git repository on each deploy is a Windmill EE feature.
|
||||
</Alert>
|
||||
<div class="mb-1" />
|
||||
{/if}
|
||||
<Alert
|
||||
type="info"
|
||||
title="Scripts, flows and apps in the user private folders will be ignored"
|
||||
>
|
||||
All scripts, flows and apps located in the workspace will be pushed to the Git repository,
|
||||
except the ones that are saved in private user folders (i.e. where the path starts with
|
||||
`u/`, use those with `f/` instead).
|
||||
<br />
|
||||
Filtering out certain sensitive folders from the sync will be available soon.
|
||||
</Alert>
|
||||
{#if $enterpriseLicense}
|
||||
<div class="flex mt-5 mb-5 gap-1">
|
||||
<Button
|
||||
color="blue"
|
||||
disabled={gitSyncSettings?.some((elmt) => emptyString(elmt.git_repo_resource_path))}
|
||||
disabled={gitSyncSettings.repositories.some((elmt) =>
|
||||
emptyString(elmt.git_repo_resource_path)
|
||||
)}
|
||||
on:click={() => {
|
||||
editWindmillGitSyncSettings()
|
||||
console.log('Saving git sync settings', gitSyncSettings)
|
||||
@@ -881,32 +922,111 @@
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{#if Array.isArray(gitSyncSettings)}
|
||||
{#each gitSyncSettings as gitSyncSettingsElmt, idx}
|
||||
<div class="flex mt-5 mb-1 gap-1 items-center text-xs">
|
||||
<h6>Repository #{idx + 1}</h6>
|
||||
|
||||
<div class="flex flex-wrap gap-20">
|
||||
<div class="max-w-md w-full">
|
||||
{#if Array.isArray(gitSyncSettings.include_path)}
|
||||
<h4 class="flex gap-2 mb-4"
|
||||
>Filter on path<Tooltip>
|
||||
Only scripts, flows and apps with their path matching one of those filters will be
|
||||
synced to the Git repositories below. The filters allow '*'' and '**' characters,
|
||||
with '*'' matching any character allowed in paths until the next slash (/) and '**'
|
||||
matching anything including slashes.
|
||||
<br />By default everything in folders will be synced.
|
||||
</Tooltip></h4
|
||||
>
|
||||
{#each gitSyncSettings.include_path as gitSyncRegexpPath, idx}
|
||||
<div class="flex mt-1 items-center">
|
||||
<input type="text" bind:value={gitSyncRegexpPath} id="arg-input-array" />
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
gitSyncSettings.include_path.splice(idx, 1)
|
||||
gitSyncSettings.include_path = [...gitSyncSettings.include_path]
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
<div class="flex mt-2">
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: XCircle }}
|
||||
iconOnly={true}
|
||||
btnClasses="mt-1"
|
||||
on:click={() => {
|
||||
gitSyncSettings.splice(idx, 1)
|
||||
gitSyncSettings = [...gitSyncSettings]
|
||||
gitSyncSettings.include_path = [...gitSyncSettings.include_path, '']
|
||||
}}
|
||||
id="git-sync-add-path-filter"
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
Add filter
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-md w-full">
|
||||
<h4 class="flex gap-2 mb-4"
|
||||
>Filter on type<Tooltip>
|
||||
On top of the filter path above, you can include only certain type of object to be
|
||||
synced with the Git repository.
|
||||
<br />By default everything is synced.
|
||||
</Tooltip></h4
|
||||
>
|
||||
<div class="flex flex-col gap-1 mt-1">
|
||||
<Toggle
|
||||
bind:checked={gitSyncSettings.include_type.scripts}
|
||||
options={{ right: 'Scripts' }}
|
||||
/>
|
||||
<Toggle
|
||||
bind:checked={gitSyncSettings.include_type.flows}
|
||||
options={{ right: 'Flows' }}
|
||||
/>
|
||||
<Toggle bind:checked={gitSyncSettings.include_type.apps} options={{ right: 'Apps' }} />
|
||||
<Toggle
|
||||
bind:checked={gitSyncSettings.include_type.folders}
|
||||
options={{ right: 'Folders' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="flex gap-2 mt-5 mb-5"
|
||||
>Repositories to sync<Tooltip>
|
||||
The changes will be deployed to all the repositories set below.
|
||||
</Tooltip></h4
|
||||
>
|
||||
{#if Array.isArray(gitSyncSettings.repositories)}
|
||||
{#each gitSyncSettings.repositories as gitSyncRepository, idx}
|
||||
<div class="flex mt-5 mb-1 gap-1 items-center text-xs">
|
||||
<h6>Repository #{idx + 1}</h6>
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
gitSyncSettings.repositories.splice(idx, 1)
|
||||
gitSyncSettings.repositories = [...gitSyncSettings.repositories]
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex mt-5 mb-1 gap-1">
|
||||
{#key gitSyncSettingsElmt}
|
||||
{#key gitSyncRepository}
|
||||
<ResourcePicker
|
||||
resourceType="git_repository"
|
||||
initialValue={gitSyncSettingsElmt.git_repo_resource_path}
|
||||
initialValue={gitSyncRepository.git_repo_resource_path}
|
||||
on:change={(ev) => {
|
||||
gitSyncSettingsElmt.git_repo_resource_path = ev.detail
|
||||
gitSyncRepository.git_repo_resource_path = ev.detail
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={emptyString(gitSyncSettingsElmt.script_path)}
|
||||
disabled={emptyString(gitSyncRepository.script_path)}
|
||||
btnClasses="w-32 text-center"
|
||||
color="dark"
|
||||
on:click={() => runGitSyncTestJob(idx)}
|
||||
@@ -915,7 +1035,7 @@
|
||||
{/key}
|
||||
</div>
|
||||
<div class="flex mb-5 text-normal text-2xs gap-1">
|
||||
{#if gitSyncSettings.filter((settings) => settings.git_repo_resource_path === gitSyncSettingsElmt.git_repo_resource_path).length > 1}
|
||||
{#if gitSyncSettings.repositories.filter((settings) => settings.git_repo_resource_path === gitSyncRepository.git_repo_resource_path).length > 1}
|
||||
<span class="text-red-700">Using the same resource twice is not allowed.</span>
|
||||
{/if}
|
||||
{#if gitSyncTestJobs[idx].status !== undefined}
|
||||
@@ -939,12 +1059,12 @@
|
||||
<div class="flex mt-5 mb-1 gap-1">
|
||||
{#if gitSyncSettings}
|
||||
<Toggle
|
||||
disabled={emptyString(gitSyncSettingsElmt?.git_repo_resource_path)}
|
||||
bind:checked={gitSyncSettingsElmt.use_individual_branch}
|
||||
disabled={emptyString(gitSyncRepository.git_repo_resource_path)}
|
||||
bind:checked={gitSyncRepository.use_individual_branch}
|
||||
options={{
|
||||
right: 'Create one branch per deployed script/flow/app',
|
||||
right: 'Create one branch per deployed object',
|
||||
rightTooltip:
|
||||
"If set, Windmill will create a unique branch per script/flow/app being pushed, prefixed with 'wm_deploy/'."
|
||||
"If set, Windmill will create a unique branch per object being pushed based on its path, prefixed with 'wm_deploy/'."
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@@ -956,11 +1076,13 @@
|
||||
<Button
|
||||
color="none"
|
||||
variant="border"
|
||||
size="xs"
|
||||
btnClasses="mt-1"
|
||||
on:click={() => {
|
||||
gitSyncSettings = [
|
||||
...gitSyncSettings,
|
||||
gitSyncSettings.repositories = [
|
||||
...gitSyncSettings.repositories,
|
||||
{
|
||||
script_path: 'hub/7953/sync-script-to-git-repo-windmill',
|
||||
script_path: 'hub/7954/sync-script-to-git-repo-windmill',
|
||||
git_repo_resource_path: '',
|
||||
use_individual_branch: false
|
||||
}
|
||||
@@ -972,22 +1094,12 @@
|
||||
status: undefined
|
||||
}
|
||||
]
|
||||
}}>Add connection</Button
|
||||
>
|
||||
<Button
|
||||
color="none"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
gitSyncSettings = [...gitSyncSettings.slice(0, -1)]
|
||||
gitSyncTestJobs = [
|
||||
...gitSyncTestJobs,
|
||||
{
|
||||
jobId: undefined,
|
||||
status: undefined
|
||||
}
|
||||
]
|
||||
}}>Delete connection</Button
|
||||
}}
|
||||
id="git-sync-add-connection"
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
Add connection
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-disabled p-4 rounded-md flex flex-col gap-1">
|
||||
|
||||
Reference in New Issue
Block a user