mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-22 07:45:09 +00:00
feat(easy-docker): add app catalog tools wizard and branch profile flow
This commit is contained in:
parent
67efd3367e
commit
8e56cbb1f9
22 changed files with 1783 additions and 348 deletions
|
|
@ -25,3 +25,26 @@ bash easy-docker.sh
|
||||||
- `--no-installation-fallback`
|
- `--no-installation-fallback`
|
||||||
- Disables GitHub binary fallback for `gum`
|
- Disables GitHub binary fallback for `gum`
|
||||||
- If package manager installation fails, the script exits with manual installation guidance
|
- If package manager installation fails, the script exits with manual installation guidance
|
||||||
|
|
||||||
|
## Apps Catalog
|
||||||
|
|
||||||
|
- App options in the wizard are read from:
|
||||||
|
- `scripts/easy-docker/config/apps.tsv`
|
||||||
|
- Format per line:
|
||||||
|
- `id<TAB>label<TAB>repo<TAB>default_branch<TAB>branches_csv`
|
||||||
|
- Example:
|
||||||
|
- `erpnext<TAB>ERPNext<TAB>https://github.com/frappe/erpnext<TAB>version-15<TAB>version-15,version-16,develop`
|
||||||
|
- The install selection in the wizard is limited to apps from this catalog.
|
||||||
|
- For each selected app, the wizard shows the configured branch list from this catalog and prompts branch selection.
|
||||||
|
|
||||||
|
## Frappe Version Profiles
|
||||||
|
|
||||||
|
- During new stack creation (after stack name), the wizard asks for a Frappe branch profile from:
|
||||||
|
- `scripts/easy-docker/config/frappe.tsv`
|
||||||
|
- Format per line:
|
||||||
|
- `id<TAB>label<TAB>frappe_branch`
|
||||||
|
- Example:
|
||||||
|
- `v16<TAB>Frappe v16 (version-16)<TAB>version-16`
|
||||||
|
- The selected `frappe_branch` is saved in stack `metadata.json` and used as default branch suggestion for app branch selection.
|
||||||
|
- In `metadata.json`, this value is stored top-level as:
|
||||||
|
- `"frappe_branch": "version-16"` (example)
|
||||||
|
|
|
||||||
7
scripts/easy-docker/config/apps.tsv
Normal file
7
scripts/easy-docker/config/apps.tsv
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# id label repo default_branch branches_csv
|
||||||
|
erpnext ERPNext https://github.com/frappe/erpnext develop version-15,version-16,develop
|
||||||
|
crm CRM https://github.com/frappe/crm develop develop,version-16,version-15
|
||||||
|
hrms HRMS https://github.com/frappe/hrms develop develop,version-16,version-15
|
||||||
|
lms LMS https://github.com/frappe/lms develop develop,version-16,version-15
|
||||||
|
helpdesk Helpdesk https://github.com/frappe/helpdesk develop develop,version-16,version-15
|
||||||
|
drive Drive https://github.com/frappe/drive develop develop,version-16,version-15
|
||||||
|
4
scripts/easy-docker/config/frappe.tsv
Normal file
4
scripts/easy-docker/config/frappe.tsv
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# id label frappe_branch
|
||||||
|
v16 Frappe v16 (version-16) version-16
|
||||||
|
v15 Frappe v15 (version-15) version-15
|
||||||
|
develop Frappe develop (develop) develop
|
||||||
|
|
|
@ -12,6 +12,8 @@ load_easy_docker_wizard_common_modules() {
|
||||||
source "${wizard_dir}/common/helpers.sh"
|
source "${wizard_dir}/common/helpers.sh"
|
||||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose.sh
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose.sh
|
||||||
source "${wizard_dir}/common/compose.sh"
|
source "${wizard_dir}/common/compose.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/frappe.sh
|
||||||
|
source "${wizard_dir}/common/frappe.sh"
|
||||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/apps.sh
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/apps.sh
|
||||||
source "${wizard_dir}/common/apps.sh"
|
source "${wizard_dir}/common/apps.sh"
|
||||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/ux.sh
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/ux.sh
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,541 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
trim_predefined_catalog_field() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local value="${2}"
|
||||||
|
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_predefined_app_id() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
*[!a-z0-9._-]*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_predefined_app_id_from_label() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local app_label="${2}"
|
||||||
|
local generated_id=""
|
||||||
|
|
||||||
|
generated_id="$(
|
||||||
|
printf '%s' "${app_label}" |
|
||||||
|
tr '[:upper:]' '[:lower:]' |
|
||||||
|
sed -E 's/[[:space:]]+/_/g; s/[^a-z0-9._-]+/_/g; s/_+/_/g; s/^_+//; s/_+$//'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if ! is_valid_predefined_app_id "${generated_id}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${generated_id}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_predefined_app_repo() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
https://* | http://* | ssh://* | git://* | git@*:* | file://*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_predefined_app_branch() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
*[!A-Za-z0-9._/-]* | .* | *..* | */ | /*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
csv_contains_branch() {
|
||||||
|
local csv_values="${1}"
|
||||||
|
local value="${2}"
|
||||||
|
|
||||||
|
case ",${csv_values}," in
|
||||||
|
*,"${value}",*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_predefined_branches_csv() {
|
||||||
|
local result_csv_var="${1}"
|
||||||
|
local branches_csv_raw="${2}"
|
||||||
|
local branch_token=""
|
||||||
|
local normalized_csv=""
|
||||||
|
local -a raw_tokens=()
|
||||||
|
|
||||||
|
IFS=',' read -r -a raw_tokens <<<"${branches_csv_raw}"
|
||||||
|
for branch_token in "${raw_tokens[@]}"; do
|
||||||
|
trim_predefined_catalog_field branch_token "${branch_token}"
|
||||||
|
if [ -z "${branch_token}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_predefined_app_branch "${branch_token}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if csv_contains_branch "${normalized_csv}" "${branch_token}"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${normalized_csv}" ]; then
|
||||||
|
normalized_csv="${branch_token}"
|
||||||
|
else
|
||||||
|
normalized_csv="${normalized_csv},${branch_token}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${normalized_csv}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_csv_var}" "%s" "${normalized_csv}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_apps_catalog_path() {
|
||||||
|
local repo_root=""
|
||||||
|
|
||||||
|
repo_root="$(get_easy_docker_repo_root)"
|
||||||
|
printf '%s/scripts/easy-docker/config/apps.tsv\n' "${repo_root}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_apps_catalog_entries() {
|
||||||
|
local catalog_path=""
|
||||||
|
local raw_line=""
|
||||||
|
local line=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
local normalized_branches_csv=""
|
||||||
|
local first_branch=""
|
||||||
|
local extra=""
|
||||||
|
local seen_ids=","
|
||||||
|
local seen_labels=","
|
||||||
|
|
||||||
|
catalog_path="$(get_predefined_apps_catalog_path)"
|
||||||
|
if [ ! -f "${catalog_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r raw_line || [ -n "${raw_line}" ]; do
|
||||||
|
trim_predefined_catalog_field line "${raw_line}"
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${line}" in
|
||||||
|
\#*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "${line}" == *$'\t'* ]]; then
|
||||||
|
IFS=$'\t' read -r app_id app_label app_repo app_default_branch app_branches_csv extra <<<"${line}"
|
||||||
|
else
|
||||||
|
# Backward compatibility for older catalog rows.
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv extra <<<"${line}"
|
||||||
|
fi
|
||||||
|
trim_predefined_catalog_field app_id "${app_id}"
|
||||||
|
trim_predefined_catalog_field app_label "${app_label}"
|
||||||
|
trim_predefined_catalog_field app_repo "${app_repo}"
|
||||||
|
trim_predefined_catalog_field app_default_branch "${app_default_branch}"
|
||||||
|
trim_predefined_catalog_field app_branches_csv "${app_branches_csv}"
|
||||||
|
trim_predefined_catalog_field extra "${extra}"
|
||||||
|
|
||||||
|
if [ -n "${extra}" ] || [ -z "${app_id}" ] || [ -z "${app_label}" ] || [ -z "${app_repo}" ] || [ -z "${app_branches_csv}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_predefined_app_id "${app_id}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_predefined_app_repo "${app_repo}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! normalize_predefined_branches_csv normalized_branches_csv "${app_branches_csv}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${app_default_branch}" ]; then
|
||||||
|
first_branch="${normalized_branches_csv%%,*}"
|
||||||
|
app_default_branch="${first_branch}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_predefined_app_branch "${app_default_branch}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! csv_contains_branch "${normalized_branches_csv}" "${app_default_branch}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${seen_ids}" in
|
||||||
|
*,"${app_id}",*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "${seen_labels}" in
|
||||||
|
*,"${app_label}",*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
seen_ids="${seen_ids}${app_id},"
|
||||||
|
seen_labels="${seen_labels}${app_label},"
|
||||||
|
|
||||||
|
printf '%s|%s|%s|%s|%s\n' "${app_id}" "${app_label}" "${app_repo}" "${app_default_branch}" "${normalized_branches_csv}"
|
||||||
|
done <"${catalog_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_app_labels_lines() {
|
||||||
|
local entry=""
|
||||||
|
local app_label=""
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
app_label="${entry#*|}"
|
||||||
|
app_label="${app_label%%|*}"
|
||||||
|
printf '%s\n' "${app_label}"
|
||||||
|
done < <(get_predefined_apps_catalog_entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_app_id_by_label() {
|
||||||
|
local label="${1}"
|
||||||
|
local entry=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv <<<"${entry}"
|
||||||
|
if [ "${app_label}" = "${label}" ]; then
|
||||||
|
printf '%s\n' "${app_id}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_predefined_apps_catalog_entries)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_app_repo_by_id() {
|
||||||
|
local app_id_lookup="${1}"
|
||||||
|
local entry=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv <<<"${entry}"
|
||||||
|
if [ "${app_id}" = "${app_id_lookup}" ]; then
|
||||||
|
printf '%s\n' "${app_repo}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_predefined_apps_catalog_entries)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_app_label_by_id() {
|
||||||
|
local app_id_lookup="${1}"
|
||||||
|
local entry=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv <<<"${entry}"
|
||||||
|
if [ "${app_id}" = "${app_id_lookup}" ]; then
|
||||||
|
printf '%s\n' "${app_label}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_predefined_apps_catalog_entries)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_app_default_branch_by_id() {
|
||||||
|
local app_id_lookup="${1}"
|
||||||
|
local entry=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv <<<"${entry}"
|
||||||
|
if [ "${app_id}" = "${app_id_lookup}" ]; then
|
||||||
|
printf '%s\n' "${app_default_branch}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_predefined_apps_catalog_entries)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_app_branch_lines_by_id() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local app_id_lookup="${2}"
|
||||||
|
local entry=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
local branch=""
|
||||||
|
local branch_lines=""
|
||||||
|
local -a branches=()
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv <<<"${entry}"
|
||||||
|
if [ "${app_id}" != "${app_id_lookup}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=',' read -r -a branches <<<"${app_branches_csv}"
|
||||||
|
for branch in "${branches[@]}"; do
|
||||||
|
trim_predefined_catalog_field branch "${branch}"
|
||||||
|
if [ -z "${branch}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -z "${branch_lines}" ]; then
|
||||||
|
branch_lines="${branch}"
|
||||||
|
else
|
||||||
|
branch_lines="${branch_lines}"$'\n'"${branch}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${branch_lines}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${branch_lines}"
|
||||||
|
return 0
|
||||||
|
done < <(get_predefined_apps_catalog_entries)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
predefined_app_catalog_has_id() {
|
||||||
|
local app_id_lookup="${1}"
|
||||||
|
local entry=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
|
||||||
|
if [ -z "${app_id_lookup}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv <<<"${entry}"
|
||||||
|
if [ "${app_id}" = "${app_id_lookup}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_predefined_apps_catalog_entries || true)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
predefined_app_catalog_has_label() {
|
||||||
|
local app_label_lookup="${1}"
|
||||||
|
local entry=""
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
|
||||||
|
if [ -z "${app_label_lookup}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r app_id app_label app_repo app_default_branch app_branches_csv <<<"${entry}"
|
||||||
|
if [ "${app_label}" = "${app_label_lookup}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_predefined_apps_catalog_entries || true)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
append_predefined_app_catalog_entry() {
|
||||||
|
local app_id="${1}"
|
||||||
|
local app_label="${2}"
|
||||||
|
local app_repo="${3}"
|
||||||
|
local app_default_branch="${4}"
|
||||||
|
local app_branches_csv="${5}"
|
||||||
|
local normalized_branches_csv=""
|
||||||
|
local first_branch=""
|
||||||
|
local catalog_path=""
|
||||||
|
local catalog_tmp_path=""
|
||||||
|
local last_char=""
|
||||||
|
|
||||||
|
if ! get_predefined_apps_catalog_entries >/dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
trim_predefined_catalog_field app_id "${app_id}"
|
||||||
|
trim_predefined_catalog_field app_label "${app_label}"
|
||||||
|
trim_predefined_catalog_field app_repo "${app_repo}"
|
||||||
|
trim_predefined_catalog_field app_default_branch "${app_default_branch}"
|
||||||
|
trim_predefined_catalog_field app_branches_csv "${app_branches_csv}"
|
||||||
|
|
||||||
|
if ! is_valid_predefined_app_id "${app_id}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ -z "${app_label}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! is_valid_predefined_app_repo "${app_repo}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! normalize_predefined_branches_csv normalized_branches_csv "${app_branches_csv}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${app_default_branch}" ]; then
|
||||||
|
first_branch="${normalized_branches_csv%%,*}"
|
||||||
|
app_default_branch="${first_branch}"
|
||||||
|
fi
|
||||||
|
if ! is_valid_predefined_app_branch "${app_default_branch}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! csv_contains_branch "${normalized_branches_csv}" "${app_default_branch}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if predefined_app_catalog_has_id "${app_id}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if predefined_app_catalog_has_label "${app_label}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
catalog_path="$(get_predefined_apps_catalog_path)"
|
||||||
|
catalog_tmp_path="${catalog_path}.tmp"
|
||||||
|
if [ ! -f "${catalog_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! cp -- "${catalog_path}" "${catalog_tmp_path}"; then
|
||||||
|
rm -f -- "${catalog_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -s "${catalog_tmp_path}" ]; then
|
||||||
|
if command_exists tail; then
|
||||||
|
last_char="$(tail -c 1 "${catalog_tmp_path}" 2>/dev/null || true)"
|
||||||
|
if [ -n "${last_char}" ]; then
|
||||||
|
if ! printf '\n' >>"${catalog_tmp_path}"; then
|
||||||
|
rm -f -- "${catalog_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! printf '\n' >>"${catalog_tmp_path}"; then
|
||||||
|
rm -f -- "${catalog_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! printf '%s\t%s\t%s\t%s\t%s\n' "${app_id}" "${app_label}" "${app_repo}" "${app_default_branch}" "${normalized_branches_csv}" >>"${catalog_tmp_path}"; then
|
||||||
|
rm -f -- "${catalog_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -- "${catalog_tmp_path}" "${catalog_path}"; then
|
||||||
|
rm -f -- "${catalog_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
persist_stack_apps_json_content() {
|
persist_stack_apps_json_content() {
|
||||||
local stack_dir="${1}"
|
local stack_dir="${1}"
|
||||||
local apps_json_content="${2}"
|
local apps_json_content="${2}"
|
||||||
|
|
@ -95,12 +631,63 @@ get_metadata_apps_custom_lines() {
|
||||||
' "${metadata_path}"
|
' "${metadata_path}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_metadata_apps_predefined_branch_lines() {
|
||||||
|
local metadata_path="${1}"
|
||||||
|
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk '
|
||||||
|
/"apps"[[:space:]]*:[[:space:]]*{/ {
|
||||||
|
in_apps = 1
|
||||||
|
}
|
||||||
|
in_apps && /"predefined_branches"[[:space:]]*:[[:space:]]*{/ {
|
||||||
|
in_predefined_branches = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_predefined_branches && /}/ {
|
||||||
|
in_predefined_branches = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_predefined_branches {
|
||||||
|
if (match($0, /"([^"]+)"[[:space:]]*:[[:space:]]*"([^"]+)"/, parts)) {
|
||||||
|
print parts[1] "|" parts[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "${metadata_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_metadata_apps_predefined_branch_for_id() {
|
||||||
|
local metadata_path="${1}"
|
||||||
|
local app_id_lookup="${2}"
|
||||||
|
local line=""
|
||||||
|
local app_id=""
|
||||||
|
local app_branch=""
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
app_id="${line%%|*}"
|
||||||
|
app_branch="${line#*|}"
|
||||||
|
if [ "${app_id}" = "${app_id_lookup}" ] && [ -n "${app_branch}" ]; then
|
||||||
|
printf '%s\n' "${app_branch}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_metadata_apps_predefined_branch_lines "${metadata_path}" || true)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
build_stack_apps_json_content_from_metadata_apps() {
|
build_stack_apps_json_content_from_metadata_apps() {
|
||||||
local result_var="${1}"
|
local result_var="${1}"
|
||||||
local stack_dir="${2}"
|
local stack_dir="${2}"
|
||||||
local metadata_path=""
|
local metadata_path=""
|
||||||
local preset_apps_csv=""
|
local preset_apps_csv=""
|
||||||
local custom_apps_lines=""
|
local custom_apps_lines=""
|
||||||
|
local predefined_branch=""
|
||||||
local preset_branch=""
|
local preset_branch=""
|
||||||
local app=""
|
local app=""
|
||||||
local line=""
|
local line=""
|
||||||
|
|
@ -120,25 +707,26 @@ build_stack_apps_json_content_from_metadata_apps() {
|
||||||
|
|
||||||
preset_apps_csv="$(get_metadata_apps_predefined_csv "${metadata_path}" || true)"
|
preset_apps_csv="$(get_metadata_apps_predefined_csv "${metadata_path}" || true)"
|
||||||
custom_apps_lines="$(get_metadata_apps_custom_lines "${metadata_path}" || true)"
|
custom_apps_lines="$(get_metadata_apps_custom_lines "${metadata_path}" || true)"
|
||||||
preset_branch="$(get_default_frappe_branch)"
|
preset_branch="$(get_stack_frappe_branch "${stack_dir}" || true)"
|
||||||
|
if [ -z "${preset_branch}" ]; then
|
||||||
|
preset_branch="$(get_default_frappe_branch)"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "${preset_apps_csv}" ]; then
|
if [ -n "${preset_apps_csv}" ]; then
|
||||||
IFS=',' read -r -a preset_apps <<<"${preset_apps_csv}"
|
IFS=',' read -r -a preset_apps <<<"${preset_apps_csv}"
|
||||||
for app in "${preset_apps[@]}"; do
|
for app in "${preset_apps[@]}"; do
|
||||||
case "${app}" in
|
url="$(get_predefined_app_repo_by_id "${app}" || true)"
|
||||||
erpnext)
|
if [ -z "${url}" ]; then
|
||||||
url="https://github.com/frappe/erpnext"
|
return 1
|
||||||
;;
|
fi
|
||||||
crm)
|
|
||||||
url="https://github.com/frappe/crm"
|
predefined_branch="$(get_metadata_apps_predefined_branch_for_id "${metadata_path}" "${app}" || true)"
|
||||||
;;
|
if [ -z "${predefined_branch}" ]; then
|
||||||
*)
|
predefined_branch="${preset_branch}"
|
||||||
continue
|
fi
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
escaped_url="$(json_escape_string "${url}")"
|
escaped_url="$(json_escape_string "${url}")"
|
||||||
escaped_branch="$(json_escape_string "${preset_branch}")"
|
escaped_branch="$(json_escape_string "${predefined_branch}")"
|
||||||
entry_json="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_url}" "${escaped_branch}")"
|
entry_json="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_url}" "${escaped_branch}")"
|
||||||
if [ -z "${entries_json}" ]; then
|
if [ -z "${entries_json}" ]; then
|
||||||
entries_json="${entry_json}"
|
entries_json="${entry_json}"
|
||||||
|
|
@ -216,9 +804,17 @@ persist_stack_metadata_apps_object() {
|
||||||
in_top_level_apps = 0
|
in_top_level_apps = 0
|
||||||
apps_depth = 0
|
apps_depth = 0
|
||||||
inserted = 0
|
inserted = 0
|
||||||
|
prev = ""
|
||||||
|
}
|
||||||
|
function flush_prev() {
|
||||||
|
if (prev != "") {
|
||||||
|
print prev
|
||||||
|
prev = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
if (!in_top_level_apps && $0 ~ /^ "apps"[[:space:]]*:/) {
|
if (!in_top_level_apps && $0 ~ /^ "apps"[[:space:]]*:/) {
|
||||||
|
flush_prev()
|
||||||
print " \"apps\": " apps_object ","
|
print " \"apps\": " apps_object ","
|
||||||
in_top_level_apps = 1
|
in_top_level_apps = 1
|
||||||
inserted = 1
|
inserted = 1
|
||||||
|
|
@ -244,18 +840,122 @@ persist_stack_metadata_apps_object() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inserted && $0 ~ /^ "wizard"[[:space:]]*:/) {
|
if (!inserted && $0 ~ /^ "wizard"[[:space:]]*:/) {
|
||||||
|
flush_prev()
|
||||||
print " \"apps\": " apps_object ","
|
print " \"apps\": " apps_object ","
|
||||||
inserted = 1
|
inserted = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inserted && $0 ~ /^}/) {
|
if (!inserted && $0 ~ /^}/) {
|
||||||
|
if (prev != "") {
|
||||||
|
if (prev !~ /,[[:space:]]*$/) {
|
||||||
|
prev = prev ","
|
||||||
|
}
|
||||||
|
print prev
|
||||||
|
prev = ""
|
||||||
|
}
|
||||||
print " \"apps\": " apps_object
|
print " \"apps\": " apps_object
|
||||||
inserted = 1
|
inserted = 1
|
||||||
|
print $0
|
||||||
|
next
|
||||||
}
|
}
|
||||||
|
|
||||||
print
|
flush_prev()
|
||||||
|
prev = $0
|
||||||
}
|
}
|
||||||
END {
|
END {
|
||||||
|
flush_prev()
|
||||||
|
if (!inserted) {
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "${metadata_path}" >"${metadata_tmp_path}"; then
|
||||||
|
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -- "${metadata_tmp_path}" "${metadata_path}"; then
|
||||||
|
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_stack_metadata_wizard_object() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local wizard_json_object="${2}"
|
||||||
|
local metadata_path=""
|
||||||
|
local metadata_tmp_path=""
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
metadata_tmp_path="${metadata_path}.tmp"
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${wizard_json_object}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! awk -v wizard_object="${wizard_json_object}" '
|
||||||
|
BEGIN {
|
||||||
|
in_top_level_wizard = 0
|
||||||
|
wizard_depth = 0
|
||||||
|
inserted = 0
|
||||||
|
prev = ""
|
||||||
|
}
|
||||||
|
function flush_prev() {
|
||||||
|
if (prev != "") {
|
||||||
|
print prev
|
||||||
|
prev = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (!in_top_level_wizard && $0 ~ /^ "wizard"[[:space:]]*:/) {
|
||||||
|
flush_prev()
|
||||||
|
print " \"wizard\": " wizard_object
|
||||||
|
in_top_level_wizard = 1
|
||||||
|
inserted = 1
|
||||||
|
if ($0 ~ /{/) {
|
||||||
|
wizard_depth += gsub(/{/, "{", $0)
|
||||||
|
wizard_depth -= gsub(/}/, "}", $0)
|
||||||
|
} else {
|
||||||
|
wizard_depth = 0
|
||||||
|
}
|
||||||
|
if (wizard_depth <= 0) {
|
||||||
|
in_top_level_wizard = 0
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_top_level_wizard) {
|
||||||
|
wizard_depth += gsub(/{/, "{", $0)
|
||||||
|
wizard_depth -= gsub(/}/, "}", $0)
|
||||||
|
if (wizard_depth <= 0) {
|
||||||
|
in_top_level_wizard = 0
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inserted && $0 ~ /^}/) {
|
||||||
|
if (prev != "") {
|
||||||
|
if (prev !~ /,[[:space:]]*$/) {
|
||||||
|
prev = prev ","
|
||||||
|
}
|
||||||
|
print prev
|
||||||
|
prev = ""
|
||||||
|
}
|
||||||
|
print " \"wizard\": " wizard_object
|
||||||
|
inserted = 1
|
||||||
|
print $0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
flush_prev()
|
||||||
|
prev = $0
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
flush_prev()
|
||||||
if (!inserted) {
|
if (!inserted) {
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@ readonly FLOW_CONTINUE=0
|
||||||
readonly FLOW_BACK_TO_MAIN=10
|
readonly FLOW_BACK_TO_MAIN=10
|
||||||
readonly FLOW_EXIT_APP=11
|
readonly FLOW_EXIT_APP=11
|
||||||
readonly FLOW_ABORT_INPUT=12
|
readonly FLOW_ABORT_INPUT=12
|
||||||
|
readonly FLOW_OPEN_MANAGE_STACK=13
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
get_easy_docker_repo_root() {
|
get_easy_docker_repo_root() {
|
||||||
local app_lib_dir=""
|
local app_lib_dir=""
|
||||||
app_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
app_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
(cd "${app_lib_dir}/../../../../.." && pwd)
|
# core.sh lives in scripts/easy-docker/lib/app/wizard/common
|
||||||
|
# so we need 6 levels up to reach repository root.
|
||||||
|
(cd "${app_lib_dir}/../../../../../.." && pwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
get_easy_docker_stacks_dir() {
|
get_easy_docker_stacks_dir() {
|
||||||
|
|
@ -35,6 +37,7 @@ create_stack_directory_with_metadata() {
|
||||||
local stack_dir_var="${1}"
|
local stack_dir_var="${1}"
|
||||||
local stack_name="${2}"
|
local stack_name="${2}"
|
||||||
local setup_type="${3:-production}"
|
local setup_type="${3:-production}"
|
||||||
|
local frappe_branch="${4:-}"
|
||||||
local stacks_dir=""
|
local stacks_dir=""
|
||||||
local created_stack_dir=""
|
local created_stack_dir=""
|
||||||
local metadata_path=""
|
local metadata_path=""
|
||||||
|
|
@ -57,11 +60,16 @@ create_stack_directory_with_metadata() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
created_at="$(get_current_utc_timestamp)"
|
created_at="$(get_current_utc_timestamp)"
|
||||||
|
if [ -z "${frappe_branch}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
if ! cat >"${metadata_path}" <<EOF; then
|
if ! cat >"${metadata_path}" <<EOF; then
|
||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"stack_name": "${stack_name}",
|
"stack_name": "${stack_name}",
|
||||||
"setup_type": "${setup_type}",
|
"setup_type": "${setup_type}",
|
||||||
|
"frappe_branch": "${frappe_branch}",
|
||||||
"created_at": "${created_at}"
|
"created_at": "${created_at}"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
@ -199,8 +207,6 @@ get_default_frappe_branch() {
|
||||||
local repo_root=""
|
local repo_root=""
|
||||||
local source_env_file=""
|
local source_env_file=""
|
||||||
local value=""
|
local value=""
|
||||||
local erpnext_version=""
|
|
||||||
local major_version=""
|
|
||||||
|
|
||||||
if [ -n "${FRAPPE_BRANCH:-}" ]; then
|
if [ -n "${FRAPPE_BRANCH:-}" ]; then
|
||||||
printf '%s\n' "${FRAPPE_BRANCH}"
|
printf '%s\n' "${FRAPPE_BRANCH}"
|
||||||
|
|
@ -216,22 +222,29 @@ get_default_frappe_branch() {
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
erpnext_version="$(get_default_erpnext_version || true)"
|
|
||||||
case "${erpnext_version}" in
|
|
||||||
v[0-9]*)
|
|
||||||
major_version="${erpnext_version#v}"
|
|
||||||
major_version="${major_version%%.*}"
|
|
||||||
if [[ "${major_version}" =~ ^[0-9]+$ ]]; then
|
|
||||||
printf 'version-%s\n' "${major_version}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
printf 'version-15\n'
|
printf 'version-15\n'
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_stack_frappe_branch() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local metadata_path=""
|
||||||
|
local value=""
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
value="$(get_metadata_string_field "${metadata_path}" "frappe_branch" || true)"
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${value}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
get_stack_env_path() {
|
get_stack_env_path() {
|
||||||
local stack_dir="${1}"
|
local stack_dir="${1}"
|
||||||
local metadata_path=""
|
local metadata_path=""
|
||||||
|
|
|
||||||
165
scripts/easy-docker/lib/app/wizard/common/frappe.sh
Executable file
165
scripts/easy-docker/lib/app/wizard/common/frappe.sh
Executable file
|
|
@ -0,0 +1,165 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
trim_frappe_catalog_field() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local value="${2}"
|
||||||
|
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_frappe_catalog_id() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
*[!a-z0-9._-]*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_frappe_branch_name() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
*[!A-Za-z0-9._/-]* | .* | *..* | */ | /*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_frappe_versions_catalog_path() {
|
||||||
|
local repo_root=""
|
||||||
|
|
||||||
|
repo_root="$(get_easy_docker_repo_root)"
|
||||||
|
printf '%s/scripts/easy-docker/config/frappe.tsv\n' "${repo_root}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_frappe_versions_catalog_entries() {
|
||||||
|
local catalog_path=""
|
||||||
|
local raw_line=""
|
||||||
|
local line=""
|
||||||
|
local version_id=""
|
||||||
|
local version_label=""
|
||||||
|
local frappe_branch=""
|
||||||
|
local extra=""
|
||||||
|
local seen_ids=","
|
||||||
|
local seen_labels=","
|
||||||
|
|
||||||
|
catalog_path="$(get_frappe_versions_catalog_path)"
|
||||||
|
if [ ! -f "${catalog_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r raw_line || [ -n "${raw_line}" ]; do
|
||||||
|
trim_frappe_catalog_field line "${raw_line}"
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${line}" in
|
||||||
|
\#*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "${line}" == *$'\t'* ]]; then
|
||||||
|
IFS=$'\t' read -r version_id version_label frappe_branch extra <<<"${line}"
|
||||||
|
else
|
||||||
|
IFS='|' read -r version_id version_label frappe_branch extra <<<"${line}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
trim_frappe_catalog_field version_id "${version_id}"
|
||||||
|
trim_frappe_catalog_field version_label "${version_label}"
|
||||||
|
trim_frappe_catalog_field frappe_branch "${frappe_branch}"
|
||||||
|
trim_frappe_catalog_field extra "${extra}"
|
||||||
|
|
||||||
|
if [ -n "${extra}" ] || [ -z "${version_id}" ] || [ -z "${version_label}" ] || [ -z "${frappe_branch}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_frappe_catalog_id "${version_id}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_frappe_branch_name "${frappe_branch}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${seen_ids}" in
|
||||||
|
*,"${version_id}",*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "${seen_labels}" in
|
||||||
|
*,"${version_label}",*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
seen_ids="${seen_ids}${version_id},"
|
||||||
|
seen_labels="${seen_labels}${version_label},"
|
||||||
|
|
||||||
|
printf '%s|%s|%s\n' "${version_id}" "${version_label}" "${frappe_branch}"
|
||||||
|
done <"${catalog_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_frappe_version_branch_by_label() {
|
||||||
|
local label_lookup="${1}"
|
||||||
|
local entry=""
|
||||||
|
local version_id=""
|
||||||
|
local version_label=""
|
||||||
|
local frappe_branch=""
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r version_id version_label frappe_branch <<<"${entry}"
|
||||||
|
if [ "${version_label}" = "${label_lookup}" ]; then
|
||||||
|
printf '%s\n' "${frappe_branch}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_frappe_versions_catalog_entries)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_frappe_version_label_by_branch() {
|
||||||
|
local branch_lookup="${1}"
|
||||||
|
local entry=""
|
||||||
|
local version_id=""
|
||||||
|
local version_label=""
|
||||||
|
local frappe_branch=""
|
||||||
|
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
if [ -z "${entry}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r version_id version_label frappe_branch <<<"${entry}"
|
||||||
|
if [ "${frappe_branch}" = "${branch_lookup}" ]; then
|
||||||
|
printf '%s\n' "${version_label}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(get_frappe_versions_catalog_entries)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
@ -83,6 +83,67 @@ prompt_stack_name_with_cancel() {
|
||||||
return "${FLOW_CONTINUE}"
|
return "${FLOW_CONTINUE}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prompt_frappe_branch_with_cancel() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local stack_name="${2}"
|
||||||
|
local options_lines=""
|
||||||
|
local selection=""
|
||||||
|
local selection_status=0
|
||||||
|
local selected_label=""
|
||||||
|
local selected_branch=""
|
||||||
|
local default_branch=""
|
||||||
|
local default_label=""
|
||||||
|
local version_entry=""
|
||||||
|
local version_id=""
|
||||||
|
local version_label=""
|
||||||
|
local version_branch=""
|
||||||
|
local -a version_entries=()
|
||||||
|
|
||||||
|
mapfile -t version_entries < <(get_frappe_versions_catalog_entries || true)
|
||||||
|
for version_entry in "${version_entries[@]}"; do
|
||||||
|
IFS='|' read -r version_id version_label version_branch <<<"${version_entry}"
|
||||||
|
if [ -z "${version_id}" ] || [ -z "${version_label}" ] || [ -z "${version_branch}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${options_lines}" ]; then
|
||||||
|
options_lines="${version_label}"
|
||||||
|
else
|
||||||
|
options_lines="$(printf '%s\n%s' "${options_lines}" "${version_label}")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${options_lines}" ]; then
|
||||||
|
show_warning_and_wait "No Frappe version profiles available in scripts/easy-docker/config/frappe.tsv." 3
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
default_branch="$(get_default_frappe_branch || true)"
|
||||||
|
default_label="$(get_frappe_version_label_by_branch "${default_branch}" || true)"
|
||||||
|
|
||||||
|
selection="$(show_frappe_version_profile_menu "${stack_name}" "${options_lines}" "${default_label}")"
|
||||||
|
selection_status=$?
|
||||||
|
if [ "${selection_status}" -ne 0 ]; then
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
selected_label="$(printf '%s' "${selection}" | tr -d '\r\n')"
|
||||||
|
case "${selected_label}" in
|
||||||
|
"" | "Back")
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
selected_branch="$(get_frappe_version_branch_by_label "${selected_label}" || true)"
|
||||||
|
if [ -z "${selected_branch}" ]; then
|
||||||
|
show_warning_and_wait "Could not resolve branch for selected profile: ${selected_label}" 2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${selected_branch}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
}
|
||||||
|
|
||||||
show_warning_and_wait() {
|
show_warning_and_wait() {
|
||||||
local message="${1}"
|
local message="${1}"
|
||||||
local seconds="${2:-1}"
|
local seconds="${2:-1}"
|
||||||
|
|
|
||||||
575
scripts/easy-docker/lib/app/wizard/env/apps.sh
vendored
575
scripts/easy-docker/lib/app/wizard/env/apps.sh
vendored
|
|
@ -1,14 +1,11 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
is_valid_git_repo_source() {
|
csv_contains_value() {
|
||||||
local value="${1}"
|
local csv_values="${1}"
|
||||||
|
local value="${2}"
|
||||||
|
|
||||||
if [ -z "${value}" ]; then
|
case ",${csv_values}," in
|
||||||
return 1
|
*,"${value}",*)
|
||||||
fi
|
|
||||||
|
|
||||||
case "${value}" in
|
|
||||||
https://* | http://* | ssh://* | git://* | git@*:* | file://*)
|
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|
@ -17,291 +14,383 @@ is_valid_git_repo_source() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
is_valid_git_branch_name() {
|
append_csv_unique() {
|
||||||
local value="${1}"
|
local result_var="${1}"
|
||||||
|
local csv_values="${2}"
|
||||||
|
local value="${3}"
|
||||||
|
local updated_csv="${csv_values}"
|
||||||
|
|
||||||
if [ -z "${value}" ]; then
|
if [ -z "${value}" ]; then
|
||||||
return 1
|
printf -v "${result_var}" "%s" "${updated_csv}"
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${value}" =~ [[:space:]] ]]; then
|
if csv_contains_value "${updated_csv}" "${value}"; then
|
||||||
return 1
|
printf -v "${result_var}" "%s" "${updated_csv}"
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "${value}" in
|
if [ -z "${updated_csv}" ]; then
|
||||||
-* | *..* | *~* | *^* | *:* | *\?* | *\[* | *\\* | */ | /* | *.)
|
updated_csv="${value}"
|
||||||
return 1
|
else
|
||||||
;;
|
updated_csv="${updated_csv},${value}"
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ "${value}" == *"@{"* ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
printf -v "${result_var}" "%s" "${updated_csv}"
|
||||||
}
|
}
|
||||||
|
|
||||||
get_predefined_app_repo_url() {
|
lines_contains_line() {
|
||||||
local app_name="${1}"
|
local lines="${1}"
|
||||||
|
local target_line="${2}"
|
||||||
|
local line=""
|
||||||
|
|
||||||
case "${app_name}" in
|
while IFS= read -r line; do
|
||||||
erpnext)
|
if [ -z "${line}" ]; then
|
||||||
printf 'https://github.com/frappe/erpnext\n'
|
continue
|
||||||
;;
|
fi
|
||||||
crm)
|
if [ "${line}" = "${target_line}" ]; then
|
||||||
printf 'https://github.com/frappe/crm\n'
|
return 0
|
||||||
;;
|
fi
|
||||||
*)
|
done <<EOF
|
||||||
return 1
|
${lines}
|
||||||
;;
|
EOF
|
||||||
esac
|
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_custom_git_apps_data() {
|
append_line_unique() {
|
||||||
local result_apps_entries_var="${1}"
|
local result_var="${1}"
|
||||||
local result_metadata_custom_var="${2}"
|
local lines="${2}"
|
||||||
local stack_dir="${3}"
|
local new_line="${3}"
|
||||||
local app_index=1
|
|
||||||
local app_count=0
|
if [ -z "${new_line}" ]; then
|
||||||
local repo_value=""
|
printf -v "${result_var}" "%s" "${lines}"
|
||||||
local branch_value=""
|
return 0
|
||||||
local repo_feedback=""
|
fi
|
||||||
local branch_feedback=""
|
|
||||||
local repo_render_context=1
|
if lines_contains_line "${lines}" "${new_line}"; then
|
||||||
local branch_render_context=1
|
printf -v "${result_var}" "%s" "${lines}"
|
||||||
local prompt_status=0
|
return 0
|
||||||
local escaped_repo=""
|
fi
|
||||||
|
|
||||||
|
if [ -z "${lines}" ]; then
|
||||||
|
printf -v "${result_var}" "%s" "${new_line}"
|
||||||
|
else
|
||||||
|
printf -v "${result_var}" "%s\n%s" "${lines}" "${new_line}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_predefined_apps_metadata_json_object() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local predefined_csv="${2}"
|
||||||
|
local branch_lines="${3}"
|
||||||
|
local app_id=""
|
||||||
|
local app_branch=""
|
||||||
|
local predefined_json_entries=""
|
||||||
|
local branch_json_entries=""
|
||||||
|
local escaped_app_id=""
|
||||||
local escaped_branch=""
|
local escaped_branch=""
|
||||||
local apps_entry=""
|
local entry_json=""
|
||||||
local metadata_entry=""
|
local line=""
|
||||||
local built_apps_entries=""
|
local -a predefined_ids=()
|
||||||
local built_metadata_custom_entries=""
|
|
||||||
|
|
||||||
while true; do
|
if [ -n "${predefined_csv}" ]; then
|
||||||
if [ -n "${repo_feedback}" ]; then
|
IFS=',' read -r -a predefined_ids <<<"${predefined_csv}"
|
||||||
repo_render_context=0
|
for app_id in "${predefined_ids[@]}"; do
|
||||||
else
|
if [ -z "${app_id}" ]; then
|
||||||
repo_render_context=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo_value="$(prompt_single_host_env_value "${stack_dir}" "CUSTOM_APP_${app_index}_REPO" "Enter git repository URL for custom app #${app_index}.\nType /done when finished or /back to return." "https://github.com/frappe/erpnext" "${repo_render_context}" "${repo_feedback}")"
|
|
||||||
prompt_status=$?
|
|
||||||
repo_feedback=""
|
|
||||||
if [ "${prompt_status}" -ne 0 ]; then
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
repo_value="$(printf '%s' "${repo_value}" | tr -d '\r\n')"
|
|
||||||
|
|
||||||
case "${repo_value}" in
|
|
||||||
/back | /BACK | /Back | /cancel | /CANCEL | /Cancel)
|
|
||||||
return 2
|
|
||||||
;;
|
|
||||||
/done | /DONE | /Done)
|
|
||||||
if [ "${app_count}" -eq 0 ]; then
|
|
||||||
repo_feedback="At least one custom app is required when 'Custom Git app(s)' is selected."
|
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
break
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if ! is_valid_git_repo_source "${repo_value}"; then
|
escaped_app_id="$(json_escape_string "${app_id}")"
|
||||||
repo_feedback="Invalid git repository URL for custom app #${app_index}."
|
entry_json="$(printf ' "%s"' "${escaped_app_id}")"
|
||||||
|
if [ -z "${predefined_json_entries}" ]; then
|
||||||
|
predefined_json_entries="${entry_json}"
|
||||||
|
else
|
||||||
|
predefined_json_entries="${predefined_json_entries}"$',\n'"${entry_json}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
branch_feedback=""
|
app_id="${line%%|*}"
|
||||||
while true; do
|
app_branch="${line#*|}"
|
||||||
if [ -n "${branch_feedback}" ]; then
|
if [ -z "${app_id}" ] || [ -z "${app_branch}" ]; then
|
||||||
branch_render_context=0
|
continue
|
||||||
else
|
|
||||||
branch_render_context=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
branch_value="$(prompt_single_host_env_value "${stack_dir}" "CUSTOM_APP_${app_index}_BRANCH" "Enter git branch for custom app #${app_index}.\nType /back to return." "main" "${branch_render_context}" "${branch_feedback}")"
|
|
||||||
prompt_status=$?
|
|
||||||
branch_feedback=""
|
|
||||||
if [ "${prompt_status}" -ne 0 ]; then
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
branch_value="$(printf '%s' "${branch_value}" | tr -d '\r\n')"
|
|
||||||
|
|
||||||
case "${branch_value}" in
|
|
||||||
/back | /BACK | /Back | /cancel | /CANCEL | /Cancel)
|
|
||||||
return 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if ! is_valid_git_branch_name "${branch_value}"; then
|
|
||||||
branch_feedback="Invalid git branch for custom app #${app_index}."
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
break
|
|
||||||
done
|
|
||||||
|
|
||||||
escaped_repo="$(json_escape_string "${repo_value}")"
|
|
||||||
escaped_branch="$(json_escape_string "${branch_value}")"
|
|
||||||
apps_entry="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_repo}" "${escaped_branch}")"
|
|
||||||
metadata_entry="$(printf ' {"repo": "%s", "branch": "%s"}' "${escaped_repo}" "${escaped_branch}")"
|
|
||||||
|
|
||||||
if [ -z "${built_apps_entries}" ]; then
|
|
||||||
built_apps_entries="${apps_entry}"
|
|
||||||
else
|
|
||||||
built_apps_entries="${built_apps_entries}"$',\n'"${apps_entry}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${built_metadata_custom_entries}" ]; then
|
escaped_app_id="$(json_escape_string "${app_id}")"
|
||||||
built_metadata_custom_entries="${metadata_entry}"
|
escaped_branch="$(json_escape_string "${app_branch}")"
|
||||||
|
entry_json="$(printf ' "%s": "%s"' "${escaped_app_id}" "${escaped_branch}")"
|
||||||
|
if [ -z "${branch_json_entries}" ]; then
|
||||||
|
branch_json_entries="${entry_json}"
|
||||||
else
|
else
|
||||||
built_metadata_custom_entries="${built_metadata_custom_entries}"$',\n'"${metadata_entry}"
|
branch_json_entries="${branch_json_entries}"$',\n'"${entry_json}"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${branch_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
printf -v "${result_var}" '{\n "predefined": [\n%s\n ],\n "predefined_branches": {\n%s\n },\n "custom": [\n ]\n }' "${predefined_json_entries}" "${branch_json_entries}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_branch_from_lines() {
|
||||||
|
local lines="${1}"
|
||||||
|
local app_id_lookup="${2}"
|
||||||
|
local line=""
|
||||||
|
local app_id=""
|
||||||
|
local app_branch=""
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
app_count=$((app_count + 1))
|
app_id="${line%%|*}"
|
||||||
app_index=$((app_index + 1))
|
app_branch="${line#*|}"
|
||||||
done
|
if [ "${app_id}" = "${app_id_lookup}" ] && [ -n "${app_branch}" ]; then
|
||||||
|
printf '%s\n' "${app_branch}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
printf -v "${result_apps_entries_var}" "%s" "${built_apps_entries}"
|
return 1
|
||||||
printf -v "${result_metadata_custom_var}" "%s" "${built_metadata_custom_entries}"
|
}
|
||||||
return 0
|
|
||||||
|
choose_predefined_app_branch() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local stack_dir="${2}"
|
||||||
|
local app_id="${3}"
|
||||||
|
local app_label="${4}"
|
||||||
|
local repo_url="${5}"
|
||||||
|
local preferred_branch="${6:-}"
|
||||||
|
local branches_lines=""
|
||||||
|
local branch=""
|
||||||
|
local status_text=""
|
||||||
|
local selection=""
|
||||||
|
local default_hint=""
|
||||||
|
local preferred_found=0
|
||||||
|
local -a branch_options=()
|
||||||
|
|
||||||
|
if ! get_predefined_app_branch_lines_by_id branches_lines "${app_id}"; then
|
||||||
|
show_warning_and_wait "No branch list configured for ${app_label} (${app_id}) in apps.tsv." 3
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r branch; do
|
||||||
|
if [ -z "${branch}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "${branch}" = "${preferred_branch}" ]; then
|
||||||
|
branch_options=("${branch}" "${branch_options[@]}")
|
||||||
|
preferred_found=1
|
||||||
|
else
|
||||||
|
branch_options+=("${branch}")
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${branches_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "${preferred_branch}" ] && [ "${preferred_found}" -eq 0 ]; then
|
||||||
|
branch_options=("${preferred_branch}" "${branch_options[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#branch_options[@]}" -eq 0 ]; then
|
||||||
|
show_warning_and_wait "No branches available for ${app_label} (${repo_url})." 3
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${preferred_branch}" ]; then
|
||||||
|
default_hint="$(printf "Suggested default: %s" "${preferred_branch}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
render_main_screen 1 >&2
|
||||||
|
status_text="$(printf "Stack: %s\n\nSelect branch for %s (%s)\nRepo: %s\n%s" "${stack_dir##*/}" "${app_label}" "${app_id}" "${repo_url}" "${default_hint}")"
|
||||||
|
render_box_message "${status_text}" "0 2" >&2
|
||||||
|
|
||||||
|
if selection="$(
|
||||||
|
gum choose \
|
||||||
|
--height 16 \
|
||||||
|
--header "Branch selection (${app_label})" \
|
||||||
|
--cursor.foreground 63 \
|
||||||
|
--selected.foreground 45 \
|
||||||
|
"${branch_options[@]}" \
|
||||||
|
"Back to app selection"
|
||||||
|
)"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${selection}" in
|
||||||
|
"Back to app selection" | "")
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf -v "${result_var}" "%s" "${selection}"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_custom_modular_apps_data() {
|
prompt_custom_modular_apps_data() {
|
||||||
local result_apps_metadata_var="${1}"
|
local result_apps_metadata_var="${1}"
|
||||||
local stack_dir="${2}"
|
local stack_dir="${2}"
|
||||||
local back_option_label="${3:-Back to topology selection}"
|
local metadata_path=""
|
||||||
|
local options_lines=""
|
||||||
|
local selected_labels_csv=""
|
||||||
local selection_raw=""
|
local selection_raw=""
|
||||||
local selection=""
|
|
||||||
local preset_apps_csv=""
|
|
||||||
local has_custom_apps=0
|
|
||||||
local prompt_status=0
|
local prompt_status=0
|
||||||
local preset_app=""
|
local selected_predefined_csv=""
|
||||||
local preset_repo_url=""
|
local parsed_predefined_csv=""
|
||||||
local preset_branch=""
|
local selected_label=""
|
||||||
local escaped_url=""
|
local predefined_app_id=""
|
||||||
local escaped_branch=""
|
local predefined_app_label=""
|
||||||
local apps_entry=""
|
local predefined_repo_url=""
|
||||||
local metadata_predefined_entry=""
|
local selected_branch=""
|
||||||
local custom_apps_entries=""
|
local preferred_branch=""
|
||||||
local metadata_custom_entries=""
|
local existing_branch_lines=""
|
||||||
local apps_entries=""
|
local selected_branch_lines=""
|
||||||
local metadata_predefined_entries=""
|
local selected_app_count=0
|
||||||
local built_apps_metadata_json_object=""
|
local built_apps_metadata_json_object=""
|
||||||
local -a selections=()
|
local -a predefined_catalog_entries=()
|
||||||
local -a preset_apps=()
|
local -a selected_predefined_ids=()
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
if [ -f "${metadata_path}" ]; then
|
||||||
|
selected_predefined_csv="$(get_metadata_apps_predefined_csv "${metadata_path}" || true)"
|
||||||
|
existing_branch_lines="$(get_metadata_apps_predefined_branch_lines "${metadata_path}" || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
selection_raw="$(show_custom_modular_apps_multi_select "${stack_dir}" "${back_option_label}" || true)"
|
options_lines=""
|
||||||
prompt_status=$?
|
selected_labels_csv=""
|
||||||
|
predefined_catalog_entries=()
|
||||||
|
|
||||||
|
mapfile -t predefined_catalog_entries < <(get_predefined_apps_catalog_entries || true)
|
||||||
|
for selected_label in "${predefined_catalog_entries[@]}"; do
|
||||||
|
IFS='|' read -r predefined_app_id predefined_app_label predefined_repo_url _ _ <<<"${selected_label}"
|
||||||
|
if [ -z "${predefined_app_id}" ] || [ -z "${predefined_app_label}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${options_lines}" ]; then
|
||||||
|
options_lines="$(printf '%s' "${predefined_app_label}")"
|
||||||
|
else
|
||||||
|
options_lines="$(printf '%s\n%s' "${options_lines}" "${predefined_app_label}")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "${selected_predefined_csv}" ]; then
|
||||||
|
IFS=',' read -r -a selected_predefined_ids <<<"${selected_predefined_csv}"
|
||||||
|
for predefined_app_id in "${selected_predefined_ids[@]}"; do
|
||||||
|
if [ -z "${predefined_app_id}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
predefined_app_label="$(get_predefined_app_label_by_id "${predefined_app_id}" || true)"
|
||||||
|
if [ -z "${predefined_app_label}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
append_csv_unique selected_labels_csv "${selected_labels_csv}" "${predefined_app_label}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${options_lines}" ]; then
|
||||||
|
show_warning_and_wait "No apps available in catalog." 3
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if selection_raw="$(show_custom_modular_apps_multi_select "${stack_dir}" "${options_lines}" "${selected_labels_csv}")"; then
|
||||||
|
prompt_status=0
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
fi
|
||||||
if [ "${prompt_status}" -ne 0 ]; then
|
if [ "${prompt_status}" -ne 0 ]; then
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${selection_raw}" ]; then
|
if [ -z "${selection_raw}" ]; then
|
||||||
show_warning_message "Select at least one app option."
|
show_warning_message "Select at least one app."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
preset_apps_csv=""
|
parsed_predefined_csv=""
|
||||||
has_custom_apps=0
|
|
||||||
custom_apps_entries=""
|
|
||||||
metadata_custom_entries=""
|
|
||||||
apps_entries=""
|
|
||||||
metadata_predefined_entries=""
|
|
||||||
|
|
||||||
mapfile -t selections <<<"${selection_raw}"
|
while IFS= read -r selected_label; do
|
||||||
for selection in "${selections[@]}"; do
|
if [ -z "${selected_label}" ]; then
|
||||||
case "${selection}" in
|
continue
|
||||||
"ERPNext")
|
fi
|
||||||
if [ -z "${preset_apps_csv}" ]; then
|
|
||||||
preset_apps_csv="erpnext"
|
predefined_app_id="$(get_predefined_app_id_by_label "${selected_label}" || true)"
|
||||||
else
|
if [ -z "${predefined_app_id}" ]; then
|
||||||
preset_apps_csv="${preset_apps_csv},erpnext"
|
continue
|
||||||
|
fi
|
||||||
|
append_csv_unique parsed_predefined_csv "${parsed_predefined_csv}" "${predefined_app_id}"
|
||||||
|
done <<EOF
|
||||||
|
${selection_raw}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
selected_predefined_csv="${parsed_predefined_csv}"
|
||||||
|
|
||||||
|
if [ -z "${selected_predefined_csv}" ]; then
|
||||||
|
show_warning_message "Select at least one app."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
selected_branch_lines=""
|
||||||
|
selected_app_count=0
|
||||||
|
IFS=',' read -r -a selected_predefined_ids <<<"${selected_predefined_csv}"
|
||||||
|
for predefined_app_id in "${selected_predefined_ids[@]}"; do
|
||||||
|
if [ -z "${predefined_app_id}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
predefined_app_label="$(get_predefined_app_label_by_id "${predefined_app_id}" || true)"
|
||||||
|
if [ -z "${predefined_app_label}" ]; then
|
||||||
|
predefined_app_label="${predefined_app_id}"
|
||||||
|
fi
|
||||||
|
predefined_repo_url="$(get_predefined_app_repo_by_id "${predefined_app_id}" || true)"
|
||||||
|
if [ -z "${predefined_repo_url}" ]; then
|
||||||
|
show_warning_and_wait "Missing repo URL for app '${predefined_app_id}'." 3
|
||||||
|
continue 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
preferred_branch="$(get_predefined_branch_from_lines "${existing_branch_lines}" "${predefined_app_id}" || true)"
|
||||||
|
if [ -z "${preferred_branch}" ]; then
|
||||||
|
preferred_branch="$(get_stack_frappe_branch "${stack_dir}" || true)"
|
||||||
|
fi
|
||||||
|
if [ -z "${preferred_branch}" ]; then
|
||||||
|
preferred_branch="$(get_predefined_app_default_branch_by_id "${predefined_app_id}" || true)"
|
||||||
|
fi
|
||||||
|
if [ -z "${preferred_branch}" ]; then
|
||||||
|
preferred_branch="$(get_default_frappe_branch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if choose_predefined_app_branch selected_branch "${stack_dir}" "${predefined_app_id}" "${predefined_app_label}" "${predefined_repo_url}" "${preferred_branch}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
if [ "${prompt_status}" -eq 2 ]; then
|
||||||
|
continue 2
|
||||||
fi
|
fi
|
||||||
;;
|
continue 2
|
||||||
"CRM")
|
fi
|
||||||
if [ -z "${preset_apps_csv}" ]; then
|
|
||||||
preset_apps_csv="crm"
|
append_line_unique selected_branch_lines "${selected_branch_lines}" "${predefined_app_id}|${selected_branch}"
|
||||||
else
|
selected_app_count=$((selected_app_count + 1))
|
||||||
preset_apps_csv="${preset_apps_csv},crm"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
"Custom Git app(s)")
|
|
||||||
has_custom_apps=1
|
|
||||||
;;
|
|
||||||
"${back_option_label}")
|
|
||||||
if [ "${#selections[@]}" -eq 1 ]; then
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
show_warning_message "Do not combine '${back_option_label}' with app selections."
|
|
||||||
preset_apps_csv=""
|
|
||||||
has_custom_apps=0
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_message "Unknown app selection: ${selection}"
|
|
||||||
preset_apps_csv=""
|
|
||||||
has_custom_apps=0
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "${preset_apps_csv}" ] && [ "${has_custom_apps}" -eq 0 ]; then
|
if [ "${selected_app_count}" -eq 0 ]; then
|
||||||
show_warning_message "Select at least one app (ERPNext, CRM, or Custom Git app)."
|
show_warning_message "No valid apps selected."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
preset_branch="$(get_default_frappe_branch)"
|
build_predefined_apps_metadata_json_object built_apps_metadata_json_object "${selected_predefined_csv}" "${selected_branch_lines}"
|
||||||
if [ -n "${preset_apps_csv}" ]; then
|
|
||||||
IFS=',' read -r -a preset_apps <<<"${preset_apps_csv}"
|
|
||||||
for preset_app in "${preset_apps[@]}"; do
|
|
||||||
preset_repo_url="$(get_predefined_app_repo_url "${preset_app}" || true)"
|
|
||||||
if [ -z "${preset_repo_url}" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
escaped_url="$(json_escape_string "${preset_repo_url}")"
|
|
||||||
escaped_branch="$(json_escape_string "${preset_branch}")"
|
|
||||||
apps_entry="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_url}" "${escaped_branch}")"
|
|
||||||
metadata_predefined_entry="$(printf ' "%s"' "${preset_app}")"
|
|
||||||
|
|
||||||
if [ -z "${apps_entries}" ]; then
|
|
||||||
apps_entries="${apps_entry}"
|
|
||||||
else
|
|
||||||
apps_entries="${apps_entries}"$',\n'"${apps_entry}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${metadata_predefined_entries}" ]; then
|
|
||||||
metadata_predefined_entries="${metadata_predefined_entry}"
|
|
||||||
else
|
|
||||||
metadata_predefined_entries="${metadata_predefined_entries}"$',\n'"${metadata_predefined_entry}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${has_custom_apps}" -eq 1 ]; then
|
|
||||||
if ! prompt_custom_git_apps_data custom_apps_entries metadata_custom_entries "${stack_dir}"; then
|
|
||||||
prompt_status=$?
|
|
||||||
return "${prompt_status}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${custom_apps_entries}" ]; then
|
|
||||||
if [ -z "${apps_entries}" ]; then
|
|
||||||
apps_entries="${custom_apps_entries}"
|
|
||||||
else
|
|
||||||
apps_entries="${apps_entries}"$',\n'"${custom_apps_entries}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${apps_entries}" ]; then
|
|
||||||
show_warning_message "No apps selected. Please choose at least one app."
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
built_apps_metadata_json_object="$(printf '{\n "predefined": [\n%s\n ],\n "custom": [\n%s\n ]\n }' "${metadata_predefined_entries}" "${metadata_custom_entries}")"
|
|
||||||
|
|
||||||
printf -v "${result_apps_metadata_var}" "%s" "${built_apps_metadata_json_object}"
|
printf -v "${result_apps_metadata_var}" "%s" "${built_apps_metadata_json_object}"
|
||||||
return 0
|
return 0
|
||||||
done
|
done
|
||||||
|
|
@ -318,7 +407,9 @@ update_stack_custom_modular_apps() {
|
||||||
return 3
|
return 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! prompt_custom_modular_apps_data apps_metadata_json_object "${stack_dir}" "Back"; then
|
if prompt_custom_modular_apps_data apps_metadata_json_object "${stack_dir}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
prompt_status=$?
|
prompt_status=$?
|
||||||
return "${prompt_status}"
|
return "${prompt_status}"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ collect_single_host_env_lines() {
|
||||||
fi
|
fi
|
||||||
collected_env_lines="$(append_env_line "${collected_env_lines}" "CUSTOM_TAG" "${custom_tag_value}")"
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "CUSTOM_TAG" "${custom_tag_value}")"
|
||||||
|
|
||||||
if ! prompt_custom_modular_apps_data selected_apps_metadata_json_object "${stack_dir}"; then
|
if prompt_custom_modular_apps_data selected_apps_metadata_json_object "${stack_dir}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
prompt_status=$?
|
prompt_status=$?
|
||||||
return "${prompt_status}"
|
return "${prompt_status}"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ load_easy_docker_wizard_flow_modules() {
|
||||||
source "${wizard_dir}/flows/manage.sh"
|
source "${wizard_dir}/flows/manage.sh"
|
||||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/navigation.sh
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/navigation.sh
|
||||||
source "${wizard_dir}/flows/navigation.sh"
|
source "${wizard_dir}/flows/navigation.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/tools.sh
|
||||||
|
source "${wizard_dir}/flows/tools.sh"
|
||||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/setup.sh
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/setup.sh
|
||||||
source "${wizard_dir}/flows/setup.sh"
|
source "${wizard_dir}/flows/setup.sh"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ handle_manage_selected_stack_flow() {
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
if persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
persist_apps_status=$?
|
persist_apps_status=$?
|
||||||
show_warning_and_wait "Could not generate ${stack_apps_path} (${persist_apps_status})." 3
|
show_warning_and_wait "Could not generate ${stack_apps_path} (${persist_apps_status})." 3
|
||||||
continue
|
continue
|
||||||
|
|
@ -42,27 +44,29 @@ handle_manage_selected_stack_flow() {
|
||||||
|
|
||||||
show_warning_and_wait "apps.json generated successfully: ${stack_apps_path}" 3
|
show_warning_and_wait "apps.json generated successfully: ${stack_apps_path}" 3
|
||||||
;;
|
;;
|
||||||
"Update custom image apps")
|
"Select apps and branches")
|
||||||
if ! update_stack_custom_modular_apps "${stack_dir}"; then
|
if update_stack_custom_modular_apps "${stack_dir}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
custom_apps_update_status=$?
|
custom_apps_update_status=$?
|
||||||
case "${custom_apps_update_status}" in
|
case "${custom_apps_update_status}" in
|
||||||
2)
|
2 | 130)
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
stack_metadata_path="${stack_dir}/metadata.json"
|
stack_metadata_path="${stack_dir}/metadata.json"
|
||||||
show_warning_and_wait "Cannot update custom image apps because metadata is missing: ${stack_metadata_path}" 3
|
show_warning_and_wait "Cannot update app selection because metadata is missing: ${stack_metadata_path}" 3
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
show_warning_and_wait "Could not update custom image apps (${custom_apps_update_status}) for stack: ${stack_name}" 3
|
show_warning_and_wait "Could not update app selection (${custom_apps_update_status}) for stack: ${stack_name}" 3
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
stack_apps_path="${stack_dir}/apps.json"
|
stack_apps_path="${stack_dir}/apps.json"
|
||||||
show_warning_and_wait "Custom image apps updated in ${stack_dir}/metadata.json and ${stack_apps_path}." 3
|
show_warning_and_wait "App selection updated in ${stack_dir}/metadata.json and ${stack_apps_path}." 3
|
||||||
;;
|
;;
|
||||||
"Back" | "")
|
"Back" | "")
|
||||||
break
|
break
|
||||||
|
|
@ -82,7 +86,9 @@ handle_manage_selected_stack_flow() {
|
||||||
case "${docker_action}" in
|
case "${docker_action}" in
|
||||||
"Generate docker compose from env")
|
"Generate docker compose from env")
|
||||||
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
||||||
if ! render_stack_compose_from_metadata "${stack_dir}"; then
|
if render_stack_compose_from_metadata "${stack_dir}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
render_compose_status=$?
|
render_compose_status=$?
|
||||||
show_warning_and_wait "Could not generate docker compose (${render_compose_status}) for ${generated_compose_path}." 3
|
show_warning_and_wait "Could not generate docker compose (${render_compose_status}) for ${generated_compose_path}." 3
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,43 @@ handle_stack_topology_flow() {
|
||||||
local stack_dir="${1}"
|
local stack_dir="${1}"
|
||||||
local topology_action=""
|
local topology_action=""
|
||||||
local abort_status=0
|
local abort_status=0
|
||||||
|
local single_host_status=0
|
||||||
|
local manage_status=0
|
||||||
|
local stack_name=""
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
topology_action="$(show_stack_topology_menu "${stack_dir}" || true)"
|
topology_action="$(show_stack_topology_menu "${stack_dir}" || true)"
|
||||||
case "${topology_action}" in
|
case "${topology_action}" in
|
||||||
"Single-host" | "Single-host (recommended)")
|
"Single-host" | "Single-host (recommended)")
|
||||||
handle_single_host_stack_flow "${stack_dir}"
|
if handle_single_host_stack_flow "${stack_dir}"; then
|
||||||
|
single_host_status="${FLOW_CONTINUE}"
|
||||||
|
else
|
||||||
|
single_host_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${single_host_status}" in
|
||||||
|
"${FLOW_OPEN_MANAGE_STACK}")
|
||||||
|
stack_name="${stack_dir##*/}"
|
||||||
|
if handle_manage_selected_stack_flow "${stack_name}"; then
|
||||||
|
manage_status="${FLOW_CONTINUE}"
|
||||||
|
else
|
||||||
|
manage_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${manage_status}" in
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
;;
|
;;
|
||||||
"Split services")
|
"Split services")
|
||||||
handle_topology_examples_flow "${topology_action}"
|
handle_topology_examples_flow "${topology_action}"
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
handle_create_new_stack_flow() {
|
handle_create_new_stack_flow() {
|
||||||
local setup_type="${1:-production}"
|
local setup_type="${1:-production}"
|
||||||
local stack_name=""
|
local stack_name=""
|
||||||
|
local frappe_branch=""
|
||||||
local stack_dir=""
|
local stack_dir=""
|
||||||
local create_stack_status=0
|
local create_stack_status=0
|
||||||
local stack_input_status=0
|
local stack_input_status=0
|
||||||
|
local branch_select_status=0
|
||||||
local topology_status=0
|
local topology_status=0
|
||||||
|
|
||||||
case "${setup_type}" in
|
case "${setup_type}" in
|
||||||
|
|
@ -39,8 +41,21 @@ handle_create_new_stack_flow() {
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
frappe_branch=""
|
||||||
|
if prompt_frappe_branch_with_cancel frappe_branch "${stack_name}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
branch_select_status=$?
|
||||||
|
if [ "${branch_select_status}" -eq "${FLOW_ABORT_INPUT}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_warning_and_wait "Could not select Frappe branch profile." 2
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
stack_dir=""
|
stack_dir=""
|
||||||
if create_stack_directory_with_metadata stack_dir "${stack_name}" "${setup_type}"; then
|
if create_stack_directory_with_metadata stack_dir "${stack_name}" "${setup_type}" "${frappe_branch}"; then
|
||||||
handle_stack_topology_flow "${stack_dir}"
|
handle_stack_topology_flow "${stack_dir}"
|
||||||
topology_status=$?
|
topology_status=$?
|
||||||
case "${topology_status}" in
|
case "${topology_status}" in
|
||||||
|
|
@ -254,6 +269,22 @@ run_easy_docker_app() {
|
||||||
*) ;;
|
*) ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
"Tools")
|
||||||
|
if handle_tools_flow; then
|
||||||
|
handler_status="${FLOW_CONTINUE}"
|
||||||
|
else
|
||||||
|
handler_status=$?
|
||||||
|
fi
|
||||||
|
case "${handler_status}" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
render_main_screen 1
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
"Environment check")
|
"Environment check")
|
||||||
if handle_environment_check_flow; then
|
if handle_environment_check_flow; then
|
||||||
handler_status="${FLOW_CONTINUE}"
|
handler_status="${FLOW_CONTINUE}"
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,11 @@ handle_single_host_stack_flow() {
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if ! save_single_host_selection "${stack_dir}" "${proxy_mode}" "${database_choice}" "${redis_choice}"; then
|
if save_single_host_selection "${stack_dir}" "${proxy_mode}" "${database_choice}" "${redis_choice}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
save_selection_status=$?
|
save_selection_status=$?
|
||||||
if [ "${save_selection_status}" -eq 2 ]; then
|
if [ "${save_selection_status}" -eq 2 ] || [ "${save_selection_status}" -eq 130 ]; then
|
||||||
return "${FLOW_CONTINUE}"
|
return "${FLOW_CONTINUE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -60,7 +62,9 @@ handle_single_host_stack_flow() {
|
||||||
return "${FLOW_CONTINUE}"
|
return "${FLOW_CONTINUE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! render_stack_compose_from_metadata "${stack_dir}"; then
|
if render_stack_compose_from_metadata "${stack_dir}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
render_compose_status=$?
|
render_compose_status=$?
|
||||||
stack_env_path="$(get_stack_env_path "${stack_dir}")"
|
stack_env_path="$(get_stack_env_path "${stack_dir}")"
|
||||||
stack_apps_path="${stack_dir}/apps.json"
|
stack_apps_path="${stack_dir}/apps.json"
|
||||||
|
|
@ -73,7 +77,7 @@ handle_single_host_stack_flow() {
|
||||||
stack_apps_path="${stack_dir}/apps.json"
|
stack_apps_path="${stack_dir}/apps.json"
|
||||||
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
||||||
show_warning_and_wait "Single-host selection saved in ${stack_dir}/metadata.json, ${stack_env_path}, and ${stack_apps_path}. Rendered compose: ${generated_compose_path}." 3
|
show_warning_and_wait "Single-host selection saved in ${stack_dir}/metadata.json, ${stack_env_path}, and ${stack_apps_path}. Rendered compose: ${generated_compose_path}." 3
|
||||||
return "${FLOW_CONTINUE}"
|
return "${FLOW_OPEN_MANAGE_STACK}"
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_topology_examples_flow() {
|
handle_topology_examples_flow() {
|
||||||
|
|
|
||||||
199
scripts/easy-docker/lib/app/wizard/flows/tools.sh
Executable file
199
scripts/easy-docker/lib/app/wizard/flows/tools.sh
Executable file
|
|
@ -0,0 +1,199 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
prompt_tools_apps_catalog_value_with_back() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local field_label="${2}"
|
||||||
|
local help_text="${3}"
|
||||||
|
local placeholder="${4:-}"
|
||||||
|
local input_value=""
|
||||||
|
local input_status=0
|
||||||
|
|
||||||
|
input_value="$(prompt_tools_apps_catalog_input "${field_label}" "${help_text}" "${placeholder}")"
|
||||||
|
input_status=$?
|
||||||
|
if [ "${input_status}" -ne 0 ]; then
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
input_value="$(printf '%s' "${input_value}" | tr -d '\r\n')"
|
||||||
|
case "${input_value}" in
|
||||||
|
/back | /BACK | /Back)
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${input_value}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_tools_apps_default_branch_from_csv_with_back() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local branches_csv="${2}"
|
||||||
|
local selection=""
|
||||||
|
local selection_status=0
|
||||||
|
local branch=""
|
||||||
|
local -a branch_options=()
|
||||||
|
|
||||||
|
IFS=',' read -r -a branch_options <<<"${branches_csv}"
|
||||||
|
if [ "${#branch_options[@]}" -eq 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
selection="$(show_tools_apps_default_branch_menu "${branch_options[@]}")"
|
||||||
|
selection_status=$?
|
||||||
|
if [ "${selection_status}" -ne 0 ]; then
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${selection}" in
|
||||||
|
"" | "Back")
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
branch="${selection}"
|
||||||
|
if ! is_valid_predefined_app_branch "${branch}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! csv_contains_branch "${branches_csv}" "${branch}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${branch}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_add_app_catalog_entry_wizard() {
|
||||||
|
local app_id=""
|
||||||
|
local app_label=""
|
||||||
|
local app_repo=""
|
||||||
|
local app_branches_csv=""
|
||||||
|
local normalized_branches_csv=""
|
||||||
|
local app_default_branch=""
|
||||||
|
local input_status=0
|
||||||
|
|
||||||
|
if ! get_predefined_apps_catalog_entries >/dev/null 2>&1; then
|
||||||
|
show_warning_and_wait "Could not load scripts/easy-docker/config/apps.tsv. Check format before adding new entries." 3
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if prompt_tools_apps_catalog_value_with_back \
|
||||||
|
app_label \
|
||||||
|
"App Label" \
|
||||||
|
"Display name used in the app selection list." \
|
||||||
|
"My Custom App"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
input_status=$?
|
||||||
|
return "${input_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
trim_predefined_catalog_field app_label "${app_label}"
|
||||||
|
if [ -z "${app_label}" ]; then
|
||||||
|
show_warning_and_wait "App label is required." 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if predefined_app_catalog_has_label "${app_label}"; then
|
||||||
|
show_warning_and_wait "App label already exists in apps.tsv: ${app_label}" 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! generate_predefined_app_id_from_label app_id "${app_label}"; then
|
||||||
|
show_warning_and_wait "Could not generate a valid app id from label. Use letters/numbers and simple separators." 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if predefined_app_catalog_has_id "${app_id}"; then
|
||||||
|
show_warning_and_wait "Generated app id already exists (${app_id}). Choose a different label." 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if prompt_tools_apps_catalog_value_with_back \
|
||||||
|
app_repo \
|
||||||
|
"Repository URL" \
|
||||||
|
"Git repository URL for this app." \
|
||||||
|
"https://github.com/acme/my-custom-app"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
input_status=$?
|
||||||
|
return "${input_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_predefined_app_repo "${app_repo}"; then
|
||||||
|
show_warning_and_wait "Invalid repository URL. Use https/http/ssh/git formats." 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if prompt_tools_apps_catalog_value_with_back \
|
||||||
|
app_branches_csv \
|
||||||
|
"Branches (CSV)" \
|
||||||
|
"Comma-separated branches for selection. Example: version-15,version-16,develop" \
|
||||||
|
"version-15,version-16,develop"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
input_status=$?
|
||||||
|
return "${input_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! normalize_predefined_branches_csv normalized_branches_csv "${app_branches_csv}"; then
|
||||||
|
show_warning_and_wait "Invalid branch list. Use a comma-separated list with valid branch names." 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if prompt_tools_apps_default_branch_from_csv_with_back app_default_branch "${normalized_branches_csv}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
input_status=$?
|
||||||
|
if [ "${input_status}" -eq "${FLOW_ABORT_INPUT}" ]; then
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
fi
|
||||||
|
show_warning_and_wait "Could not select default branch from branch list." 2
|
||||||
|
return "${input_status}"
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! append_predefined_app_catalog_entry "${app_id}" "${app_label}" "${app_repo}" "${app_default_branch}" "${normalized_branches_csv}"; then
|
||||||
|
show_warning_and_wait "Could not append app entry to scripts/easy-docker/config/apps.tsv." 3
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_warning_and_wait "App added to apps.tsv: ${app_label} (${app_id})" 2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_tools_flow() {
|
||||||
|
local tools_action=""
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
tools_action="$(show_tools_menu || true)"
|
||||||
|
|
||||||
|
case "${tools_action}" in
|
||||||
|
"Add Apps for App Selection")
|
||||||
|
run_add_app_catalog_entry_wizard || true
|
||||||
|
;;
|
||||||
|
"Back to main menu" | "")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"Exit and close easy-docker")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown tools action: ${tools_action}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
@ -155,56 +155,19 @@ persist_single_host_selection_metadata() {
|
||||||
local database_id="${3}"
|
local database_id="${3}"
|
||||||
local redis_id="${4}"
|
local redis_id="${4}"
|
||||||
local compose_files_lines="${5}"
|
local compose_files_lines="${5}"
|
||||||
local apps_json_object="${6}"
|
local env_lines="${6}"
|
||||||
local env_lines="${7}"
|
|
||||||
local metadata_path=""
|
|
||||||
local metadata_tmp_path=""
|
|
||||||
local schema_version=""
|
|
||||||
local stack_name=""
|
|
||||||
local setup_type=""
|
|
||||||
local created_at=""
|
|
||||||
local updated_at=""
|
local updated_at=""
|
||||||
local compose_files_json=""
|
local compose_files_json=""
|
||||||
local env_json_object=""
|
local env_json_object=""
|
||||||
|
local wizard_json_object=""
|
||||||
metadata_path="${stack_dir}/metadata.json"
|
|
||||||
metadata_tmp_path="${metadata_path}.tmp"
|
|
||||||
|
|
||||||
schema_version="$(get_metadata_number_field "${metadata_path}" "schema_version" || true)"
|
|
||||||
if [ -z "${schema_version}" ]; then
|
|
||||||
schema_version="1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
stack_name="$(get_metadata_string_field "${metadata_path}" "stack_name" || true)"
|
|
||||||
if [ -z "${stack_name}" ]; then
|
|
||||||
stack_name="${stack_dir##*/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
setup_type="$(get_metadata_string_field "${metadata_path}" "setup_type" || true)"
|
|
||||||
if [ -z "${setup_type}" ]; then
|
|
||||||
setup_type="production"
|
|
||||||
fi
|
|
||||||
|
|
||||||
created_at="$(get_metadata_string_field "${metadata_path}" "created_at" || true)"
|
|
||||||
if [ -z "${created_at}" ]; then
|
|
||||||
created_at="$(get_current_utc_timestamp)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
updated_at="$(get_current_utc_timestamp)"
|
updated_at="$(get_current_utc_timestamp)"
|
||||||
compose_files_json="$(build_compose_files_json_array "${compose_files_lines}")"
|
compose_files_json="$(build_compose_files_json_array "${compose_files_lines}")"
|
||||||
env_json_object="$(build_env_json_object "${env_lines}")"
|
env_json_object="$(build_env_json_object "${env_lines}")"
|
||||||
if [ -z "${apps_json_object}" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! cat >"${metadata_tmp_path}" <<EOF; then
|
if ! wizard_json_object="$(
|
||||||
|
cat <<EOF
|
||||||
{
|
{
|
||||||
"schema_version": ${schema_version},
|
|
||||||
"stack_name": "${stack_name}",
|
|
||||||
"setup_type": "${setup_type}",
|
|
||||||
"created_at": "${created_at}",
|
|
||||||
"apps": ${apps_json_object},
|
|
||||||
"wizard": {
|
|
||||||
"topology": "single-host",
|
"topology": "single-host",
|
||||||
"selection": {
|
"selection": {
|
||||||
"proxy_mode_id": "${proxy_mode_id}",
|
"proxy_mode_id": "${proxy_mode_id}",
|
||||||
|
|
@ -217,14 +180,12 @@ ${compose_files_json}
|
||||||
],
|
],
|
||||||
"updated_at": "${updated_at}"
|
"updated_at": "${updated_at}"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
EOF
|
EOF
|
||||||
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
)"; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! mv -- "${metadata_tmp_path}" "${metadata_path}"; then
|
if ! persist_stack_metadata_wizard_object "${stack_dir}" "${wizard_json_object}"; then
|
||||||
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -245,6 +206,8 @@ save_single_host_selection() {
|
||||||
local compose_files_lines=""
|
local compose_files_lines=""
|
||||||
local env_lines=""
|
local env_lines=""
|
||||||
local apps_metadata_json_object=""
|
local apps_metadata_json_object=""
|
||||||
|
local wizard_metadata_status=0
|
||||||
|
local apps_metadata_status=0
|
||||||
local collect_env_status=0
|
local collect_env_status=0
|
||||||
|
|
||||||
proxy_mode_id="$(get_single_host_proxy_mode_id "${proxy_mode}")" || return 1
|
proxy_mode_id="$(get_single_host_proxy_mode_id "${proxy_mode}")" || return 1
|
||||||
|
|
@ -261,7 +224,9 @@ save_single_host_selection() {
|
||||||
fi
|
fi
|
||||||
compose_files_lines="$(printf '%s\n%s' "${compose_files_lines}" "${proxy_overrides}")"
|
compose_files_lines="$(printf '%s\n%s' "${compose_files_lines}" "${proxy_overrides}")"
|
||||||
|
|
||||||
if ! collect_single_host_env_lines env_lines apps_metadata_json_object "${stack_dir}" "${proxy_mode_id}" "${database_id}"; then
|
if collect_single_host_env_lines env_lines apps_metadata_json_object "${stack_dir}" "${proxy_mode_id}" "${database_id}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
collect_env_status=$?
|
collect_env_status=$?
|
||||||
return "${collect_env_status}"
|
return "${collect_env_status}"
|
||||||
fi
|
fi
|
||||||
|
|
@ -270,17 +235,30 @@ save_single_host_selection() {
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! persist_single_host_selection_metadata \
|
if persist_single_host_selection_metadata \
|
||||||
"${stack_dir}" \
|
"${stack_dir}" \
|
||||||
"${proxy_mode_id}" \
|
"${proxy_mode_id}" \
|
||||||
"${database_id}" \
|
"${database_id}" \
|
||||||
"${redis_id}" \
|
"${redis_id}" \
|
||||||
"${compose_files_lines}" \
|
"${compose_files_lines}" \
|
||||||
"${apps_metadata_json_object}" \
|
|
||||||
"${env_lines}"; then
|
"${env_lines}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
wizard_metadata_status=$?
|
||||||
|
return "${wizard_metadata_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${apps_metadata_json_object}" ]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if persist_stack_metadata_apps_object "${stack_dir}" "${apps_metadata_json_object}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
apps_metadata_status=$?
|
||||||
|
return "${apps_metadata_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ load_ui_screen_modules() {
|
||||||
source "${screens_dir}/production.sh"
|
source "${screens_dir}/production.sh"
|
||||||
# shellcheck source=scripts/easy-docker/lib/ui/screens/environment.sh
|
# shellcheck source=scripts/easy-docker/lib/ui/screens/environment.sh
|
||||||
source "${screens_dir}/environment.sh"
|
source "${screens_dir}/environment.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/ui/screens/tools.sh
|
||||||
|
source "${screens_dir}/tools.sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
load_ui_screen_modules
|
load_ui_screen_modules
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,13 @@ render_main_screen() {
|
||||||
|
|
||||||
show_main_menu() {
|
show_main_menu() {
|
||||||
gum choose \
|
gum choose \
|
||||||
--height 8 \
|
--height 10 \
|
||||||
--header "Choose an action" \
|
--header "Choose an action" \
|
||||||
--cursor.foreground 63 \
|
--cursor.foreground 63 \
|
||||||
--selected.foreground 45 \
|
--selected.foreground 45 \
|
||||||
"Production Stack" \
|
"Production Stack" \
|
||||||
"Development Stack" \
|
"Development Stack" \
|
||||||
|
"Tools" \
|
||||||
"Environment check" \
|
"Environment check" \
|
||||||
"Exit"
|
"Exit"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,46 @@ prompt_new_stack_name() {
|
||||||
--placeholder "my-production-stack"
|
--placeholder "my-production-stack"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_frappe_version_profile_menu() {
|
||||||
|
local stack_name="${1}"
|
||||||
|
local options_lines="${2:-}"
|
||||||
|
local selected_label="${3:-}"
|
||||||
|
local status_text=""
|
||||||
|
local option_line=""
|
||||||
|
local -a menu_options=()
|
||||||
|
local -a gum_args=()
|
||||||
|
|
||||||
|
render_main_screen 1 >&2
|
||||||
|
|
||||||
|
status_text="$(printf "Create stack: %s\n\nSelect the Frappe branch profile from frappe.tsv.\nThis sets the stack default for branch suggestions." "${stack_name}")"
|
||||||
|
render_box_message "${status_text}" "0 2" >&2
|
||||||
|
|
||||||
|
while IFS= read -r option_line; do
|
||||||
|
if [ -z "${option_line}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
menu_options+=("${option_line}")
|
||||||
|
done <<EOF
|
||||||
|
${options_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "${#menu_options[@]}" -eq 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
gum_args=(
|
||||||
|
--height 10
|
||||||
|
--header "Frappe branch profile"
|
||||||
|
--cursor.foreground 63
|
||||||
|
--selected.foreground 45
|
||||||
|
)
|
||||||
|
if [ -n "${selected_label}" ]; then
|
||||||
|
gum_args+=(--selected "${selected_label}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
gum choose "${gum_args[@]}" "${menu_options[@]}" "Back"
|
||||||
|
}
|
||||||
|
|
||||||
show_stack_topology_menu() {
|
show_stack_topology_menu() {
|
||||||
local stack_dir="${1}"
|
local stack_dir="${1}"
|
||||||
local stack_name=""
|
local stack_name=""
|
||||||
|
|
@ -149,26 +189,45 @@ show_single_host_redis_menu() {
|
||||||
|
|
||||||
show_custom_modular_apps_multi_select() {
|
show_custom_modular_apps_multi_select() {
|
||||||
local stack_dir="${1}"
|
local stack_dir="${1}"
|
||||||
local back_option_label="${2:-Back to topology selection}"
|
local options_lines="${2:-}"
|
||||||
|
local selected_labels_csv="${3:-}"
|
||||||
local stack_name=""
|
local stack_name=""
|
||||||
local status_text=""
|
local status_text=""
|
||||||
|
local option_line=""
|
||||||
|
local -a menu_options=()
|
||||||
|
local -a gum_args=()
|
||||||
|
|
||||||
render_main_screen 1 >&2
|
render_main_screen 1 >&2
|
||||||
|
|
||||||
stack_name="${stack_dir##*/}"
|
stack_name="${stack_dir##*/}"
|
||||||
status_text="$(printf "Stack: %s\n\nCustom modular apps\nSelect one or more options.\nUse Space to toggle and Enter to confirm." "${stack_name}")"
|
status_text="$(printf "Stack: %s\n\nApps\nUse Space to toggle apps from apps.tsv. Press Enter to continue to branch selection per app.\nUse Ctrl+C to go back." "${stack_name}")"
|
||||||
render_box_message "${status_text}" "0 2" >&2
|
render_box_message "${status_text}" "0 2" >&2
|
||||||
|
|
||||||
gum choose \
|
while IFS= read -r option_line; do
|
||||||
--no-limit \
|
if [ -z "${option_line}" ]; then
|
||||||
--height 10 \
|
continue
|
||||||
--header "Custom modular apps" \
|
fi
|
||||||
--cursor.foreground 63 \
|
menu_options+=("${option_line}")
|
||||||
--selected.foreground 45 \
|
done <<EOF
|
||||||
"ERPNext" \
|
${options_lines}
|
||||||
"CRM" \
|
EOF
|
||||||
"Custom Git app(s)" \
|
|
||||||
"${back_option_label}"
|
if [ "${#menu_options[@]}" -eq 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
gum_args=(
|
||||||
|
--no-limit
|
||||||
|
--height 14
|
||||||
|
--header "Apps"
|
||||||
|
--cursor.foreground 63
|
||||||
|
--selected.foreground 45
|
||||||
|
)
|
||||||
|
if [ -n "${selected_labels_csv}" ]; then
|
||||||
|
gum_args+=(--selected "${selected_labels_csv}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
gum choose "${gum_args[@]}" "${menu_options[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_single_host_env_value() {
|
prompt_single_host_env_value() {
|
||||||
|
|
@ -324,7 +383,7 @@ show_manage_stack_apps_menu() {
|
||||||
--cursor.foreground 63 \
|
--cursor.foreground 63 \
|
||||||
--selected.foreground 45 \
|
--selected.foreground 45 \
|
||||||
"Generate apps.json" \
|
"Generate apps.json" \
|
||||||
"Update custom image apps" \
|
"Select apps and branches" \
|
||||||
"Back" \
|
"Back" \
|
||||||
"Exit and close easy-docker"
|
"Exit and close easy-docker"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
scripts/easy-docker/lib/ui/screens/tools.sh
Executable file
53
scripts/easy-docker/lib/ui/screens/tools.sh
Executable file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
show_tools_menu() {
|
||||||
|
local status_text=""
|
||||||
|
|
||||||
|
render_main_screen 1 >&2
|
||||||
|
|
||||||
|
status_text="$(printf "Tools\n\nManage helper wizards for easy-docker.\nUse this area to maintain the app catalog shown in app selection.")"
|
||||||
|
render_box_message "${status_text}" "0 2" >&2
|
||||||
|
|
||||||
|
gum choose \
|
||||||
|
--height 8 \
|
||||||
|
--header "Tools - App Catalog Utilities" \
|
||||||
|
--cursor.foreground 63 \
|
||||||
|
--selected.foreground 45 \
|
||||||
|
"Add Apps for App Selection" \
|
||||||
|
"Back to main menu" \
|
||||||
|
"Exit and close easy-docker"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_tools_apps_catalog_input() {
|
||||||
|
local field_label="${1}"
|
||||||
|
local help_text="${2}"
|
||||||
|
local placeholder="${3:-}"
|
||||||
|
local status_text=""
|
||||||
|
|
||||||
|
render_main_screen 1 >&2
|
||||||
|
|
||||||
|
status_text="$(printf "Tools\n\nAdd Apps for App Selection\nThis wizard updates scripts/easy-docker/config/apps.tsv used by app selection.\n\n%s\nType /back or press Ctrl+C to cancel." "${help_text}")"
|
||||||
|
render_box_message "${status_text}" "0 2" >&2
|
||||||
|
|
||||||
|
gum input \
|
||||||
|
--header "${field_label}" \
|
||||||
|
--prompt "value> " \
|
||||||
|
--placeholder "${placeholder}"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_tools_apps_default_branch_menu() {
|
||||||
|
local status_text=""
|
||||||
|
|
||||||
|
render_main_screen 1 >&2
|
||||||
|
|
||||||
|
status_text="$(printf "Tools\n\nAdd Apps for App Selection\nSelect the default branch from the configured branch list.\nUse Ctrl+C or choose Back to return.")"
|
||||||
|
render_box_message "${status_text}" "0 2" >&2
|
||||||
|
|
||||||
|
gum choose \
|
||||||
|
--height 14 \
|
||||||
|
--header "Default Branch - Choose from List" \
|
||||||
|
--cursor.foreground 63 \
|
||||||
|
--selected.foreground 45 \
|
||||||
|
"$@" \
|
||||||
|
"Back"
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue