diff --git a/UPGRADE.md b/UPGRADE.md
new file mode 100644
index 0000000..ab0e7e3
--- /dev/null
+++ b/UPGRADE.md
@@ -0,0 +1,128 @@
+# Upgrade to V4
+
+## Overview
+
+In the new ns8-erpnext we build docker at runtime where you pass in the apps.json you need to build your app.
+
+## Architecture
+
+The module uses a runtime Docker image building approach where:
+
+- Custom apps are defined via JSON configuration
+- Docker images are built on-demand based on the apps.json configuration
+- Podman is used for container management
+
+## Configuration Settings UI
+
+The Settings page (`ui/src/views/Settings.vue`) provides the following configuration options:
+
+### 1. FQDN (Fully Qualified Domain Name)
+
+- **Field**: Host/URL input
+- **Purpose**: Set the URL where ERPNext will be accessible
+- **Example**: `erpnext.example.org`
+- **Validation**: Required field
+
+### 2. TLS/SSL Configuration
+
+- **Let's Encrypt**: Toggle to enable automatic SSL certificate generation
+- **HTTP to HTTPS Redirect**: Toggle to force HTTPS redirects (enabled by default)
+
+### 3. Frappe Version Selection
+
+- **Options**:
+ - `version-15` (default)
+ - `version-16`
+- **Purpose**: Select the Frappe framework version to use
+
+### 4. App Management
+
+#### Adding Custom Apps
+
+Apps can be added via the UI modal with the following fields:
+
+- **App Name** (required): The application identifier
+- **Repository URL** (required): Git repository URL for the app
+- **Branch**: Git branch to use (defaults to "main")
+- **Labels**: Comma-separated labels for the app
+
+#### App Management Features
+
+- **Add App via Form**: Opens a modal to add new apps
+- **Edit App**: Modify existing app details
+- **Remove App**: Delete apps from the configuration
+- **Copy JSON**: Copy the current apps.json to clipboard
+- **JSON Editor**: Advanced users can directly edit the apps.json in the accordion section
+
+#### Apps Display
+
+Apps are displayed in a structured list showing:
+
+- App Name
+- URL
+- Branch
+- Labels
+- Actions (Edit/Remove buttons)
+
+### 5. ERPNext Modules Selection
+
+- **Multi-select component** showing available modules
+- **Dynamic population**: Options are generated from the apps.json configuration
+- **Pre-selected values**: Previously selected modules are restored when loading configuration
+- **Filtering**: When apps are removed, their modules are automatically deselected
+
+### 6. Podman Images (Advanced)
+
+- **View**: Lists all built Podman images with details (Repository, Tag, ID, Created, Size)
+- **Refresh**: Button to refresh the images list
+- **Purpose**: Monitor built Docker images
+
+## Data Flow
+
+1. **Configuration Loading** (`getConfiguration`):
+ - Fetches current configuration from backend
+ - Decodes base64 appJson
+ - Restores selected modules
+ - Populates all form fields
+
+2. **App JSON Processing**:
+ - Stored as base64 encoded string in backend
+ - Parsed into structured list for display
+ - Used to generate multi-select options
+ - Filters selected modules to only include valid apps
+
+3. **Configuration Saving** (`configureModule`):
+ - Validates host field
+ - Encodes appJson to base64
+ - Sends all configuration data to backend
+ - Triggers module reconfiguration
+
+## JSON Format
+
+The `app_json` field expects an array of app objects:
+
+```json
+[
+ {
+ "app_name": "my-custom-app",
+ "url": "https://github.com/user/repo",
+ "branch": "main",
+ "labels": "production,custom"
+ }
+]
+```
+
+## Important Notes
+
+1. **App Name Consistency**: The multi-select uses `app_name` or `name` field to match selected modules with available options
+2. **Base64 Encoding**: The appJson is base64 encoded when sent to the backend
+3. **Validation**: Apps must have at least an app_name and URL
+4. **Podman Integration**: The module interfaces with Podman for container management
+5. **Dynamic Options**: The ERPNext Modules multi-select options are dynamically generated from the apps.json
+
+**NOTE**
+
+- Always ensure apps.json is valid JSON before saving
+- Selected modules are filtered to only include apps that exist in the configuration
+- Podman images are specific to the configured apps and Frappe version
+- Make sure you install the apps that were previously installed to avoid installtion issues
diff --git a/build-images.sh b/build-images.sh
index 2e48cad..64309b4 100644
--- a/build-images.sh
+++ b/build-images.sh
@@ -15,8 +15,6 @@ repobase="${REPOBASE:-ghcr.io/geniusdynamics}"
# Configure the image name
reponame="erpnext"
-app_version="15.92.1"
-
# Create a new empty container image
container=$(buildah from scratch)
@@ -47,7 +45,7 @@ buildah config --entrypoint=/ \
--label="org.nethserver.authorizations=traefik@node:routeadm" \
--label="org.nethserver.tcp-ports-demand=1" \
--label="org.nethserver.rootfull=0" \
- --label="org.nethserver.images=docker.io/mariadb:10.11.5 docker.io/geniusdynamics/erpnext:${app_version} docker.io/redis:6.2-alpine" \
+ --label="org.nethserver.images=docker.io/mariadb:10.11.5 docker.io/redis:6.2-alpine" \
"${container}"
# Commit the image
buildah commit "${container}" "${repobase}/${reponame}"
diff --git a/imageroot/actions/build-docker-image/20configure_build_vars b/imageroot/actions/build-docker-image/20configure_build_vars
new file mode 100755
index 0000000..62235c0
--- /dev/null
+++ b/imageroot/actions/build-docker-image/20configure_build_vars
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (C) 2022 Nethesis S.r.l.
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+import json
+import sys
+import agent
+
+# Try to parse the stdin as JSON.
+# If parsing fails, output everything to stderr
+data = json.load(sys.stdin)
+
+# This is specific to you module, so you need to change it accordingly.
+
+ERP_NEXT_MODULES = data.get("erpSelectedModules", [])
+APP_JSON = data.get("appJson")
+FRAPPE_VERSION = data.get("frappeVersion", "version-15")
+
+agent.write_envfile(
+ "erpnext-modules.env", {"ERP_NEXT_MODULES": ",".join(ERP_NEXT_MODULES) if ERP_NEXT_MODULES else "", "APPS_JSON": APP_JSON, "FRAPPE_VERSION": FRAPPE_VERSION}
+)
+
+agent.dump_env()
diff --git a/imageroot/actions/build-docker-image/30build_docker_image b/imageroot/actions/build-docker-image/30build_docker_image
new file mode 100755
index 0000000..baa248a
--- /dev/null
+++ b/imageroot/actions/build-docker-image/30build_docker_image
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+echo "Building Docker Image"
+../configure-module/30build_docker_image
+
+echo "Docker Image Built"
diff --git a/imageroot/actions/configure-module/10configure_environment_vars b/imageroot/actions/configure-module/10configure_environment_vars
index fddab34..68eed19 100755
--- a/imageroot/actions/configure-module/10configure_environment_vars
+++ b/imageroot/actions/configure-module/10configure_environment_vars
@@ -13,12 +13,16 @@ import agent
# If parsing fails, output everything to stderr
data = json.load(sys.stdin)
-#This is specific to you module, so you need to change it accordingly.
+# This is specific to you module, so you need to change it accordingly.
ERP_NEXT_MODULES = data.get("erpSelectedModules", [])
+APP_JSON = data.get("appJson")
+FRAPPE_VERSION = data.get("frappeVersion", "version-15")
-agent.write_envfile("erpnext-modules.env", {
- "ERP_NEXT_MODULES": ERP_NEXT_MODULES
-})
+ERP_NEXT_MODULES_STR = ",".join(ERP_NEXT_MODULES) if ERP_NEXT_MODULES else ""
+
+agent.write_envfile(
+ "erpnext-modules.env", {"ERP_NEXT_MODULES": ERP_NEXT_MODULES_STR, "APPS_JSON": APP_JSON, "FRAPPE_VERSION": FRAPPE_VERSION}
+)
agent.dump_env()
diff --git a/imageroot/actions/configure-module/30build_docker_image b/imageroot/actions/configure-module/30build_docker_image
new file mode 100755
index 0000000..7c7c0ed
--- /dev/null
+++ b/imageroot/actions/configure-module/30build_docker_image
@@ -0,0 +1,178 @@
+#!/bin/bash
+
+set -e
+source erpnext-modules.env
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Function to print colored output
+print_info() {
+ echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Check if podman is installed
+if ! command -v podman &>/dev/null; then
+ print_error "podman is required but not installed. Please install podman first."
+ exit 1
+fi
+
+# Check if jq is installed
+if ! command -v jq &>/dev/null; then
+ print_error "jq is required but not installed. Please install jq first."
+ exit 1
+fi
+
+# Function to fetch latest ERPNext tag from GitHub based on FRAPPE_VERSION
+fetch_erpnext_tag() {
+ local major_version="${FRAPPE_VERSION##version-}"
+ local api_url="https://api.github.com/repos/frappe/erpnext/tags"
+
+ local tags_json
+ if ! tags_json=$(curl -fsSL "$api_url" 2>/dev/null); then
+ print_warning "Failed to fetch tags from GitHub, using default"
+ return 1
+ fi
+
+ local first_tag
+ if ! first_tag=$(echo "$tags_json" | jq -r ".[] | select(.name | startswith(\"v${major_version}.\")) | .name" 2>/dev/null | head -1); then
+ print_warning "Failed to parse tags, using default"
+ return 1
+ fi
+
+ if [ -z "$first_tag" ] || [ "$first_tag" = "null" ]; then
+ print_warning "No v${major_version} tags found, using default"
+ return 1
+ fi
+
+ echo "$first_tag"
+ return 0
+}
+
+# Cache file for app_json hash
+CACHE_FILE=".app_json_cache"
+CURRENT_APPS_JSON="$APPS_JSON"
+
+# Function to compute hash of the apps JSON
+compute_apps_hash() {
+ echo -n "$1" | sha256sum | cut -d' ' -f1
+}
+
+# Get current apps JSON hash
+CURRENT_HASH=$(compute_apps_hash "$CURRENT_APPS_JSON")
+
+# Check if cache exists and compare
+if [ -f "$CACHE_FILE" ]; then
+ CACHED_HASH=$(cat "$CACHE_FILE")
+ if [ "$CURRENT_HASH" = "$CACHED_HASH" ]; then
+ print_warning "App configuration unchanged. Skipping Docker image build."
+ print_info "To force a rebuild, delete the cache file: rm $CACHE_FILE"
+ exit 0
+ else
+ print_info "App configuration changed. Proceeding with Docker image build..."
+ fi
+else
+ print_info "No cache found. Building Docker image..."
+fi
+
+# Fetch latest ERPNext tag from GitHub based on FRAPPE_VERSION
+ERPNEXT_TAG=$(fetch_erpnext_tag) || true
+
+if [ -z "${ERPNEXT_TAG:-}" ]; then
+ print_warning "Could not fetch ERPNext tag, using fallback"
+ ERPNEXT_TAG="v${FRAPPE_VERSION##version-}.0.0"
+fi
+
+print_info "Using ERPNext tag: $ERPNEXT_TAG"
+
+ERPNEXT_IMAGE="frappe/erpnext:${ERPNEXT_TAG}"
+
+print_info "ERPNEXT_IMAGE forcibly set to: $ERPNEXT_IMAGE"
+# Extract image name and tag from ERPNEXT_IMAGE
+BASE_IMAGE="$ERPNEXT_IMAGE"
+# Extract repository and tag
+IMAGE_REPO=$(echo "$ERPNEXT_IMAGE" | cut -d':' -f1)
+IMAGE_TAG=$(echo "$ERPNEXT_IMAGE" | cut -d':' -f2)
+
+# Use the same tag for custom image
+CUSTOM_IMAGE_NAME="${IMAGE_REPO}"
+CUSTOM_IMAGE_TAG="$IMAGE_TAG"
+
+print_info "Base Image: $BASE_IMAGE"
+print_info "Custom Image will be: $CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"
+
+print_info "Apps to be installed:"
+echo "$APPS_JSON"
+
+FRAPPE_BRANCH="${FRAPPE_VERSION}"
+
+print_info "Using Frappe branch: $FRAPPE_BRANCH"
+print_info "Building custom ERPNext image with Podman..."
+
+# Build the image with Podman
+podman build --network=host \
+ --jobs=4 \
+ --build-arg APPS_JSON_BASE64="$APPS_JSON" \
+ --build-arg FRAPPE_BRANCH="$FRAPPE_BRANCH" \
+ -t "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" \
+ -f ../actions/configure-module/Dockerfile \
+ .
+
+if [ $? -eq 0 ]; then
+ print_info "Build completed successfully!"
+ print_info "Image tagged as: $CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"
+
+ # Also tag as latest
+ podman tag "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" "$CUSTOM_IMAGE_NAME:latest"
+
+ # Tag with the same name as base image (for drop-in replacement)
+ podman tag "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" "$BASE_IMAGE"
+ print_info "Also tagged as: $BASE_IMAGE (drop-in replacement)"
+
+ echo ""
+ print_info "To push the image to a registry:"
+ echo " podman push $CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"
+ echo " podman push $CUSTOM_IMAGE_NAME:latest"
+ echo " podman push $BASE_IMAGE"
+
+ echo ""
+ print_info "Image size:"
+ podman images "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
+ ENV_FILE="environment"
+
+ # Ensure env file exists
+ [ -f "$ENV_FILE" ] || {
+ print_error "Env file not found"
+ exit 1
+ }
+
+ CUSTOM_IMAGE_FULL="$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"
+
+ # Update or add ERPNEXT_IMAGE
+ if grep -q '^ERPNEXT_IMAGE=' "$ENV_FILE"; then
+ sed -i "s|^ERPNEXT_IMAGE=.*|ERPNEXT_IMAGE=$CUSTOM_IMAGE_FULL|" "$ENV_FILE"
+ else
+ echo "ERPNEXT_IMAGE=$CUSTOM_IMAGE_FULL" >>"$ENV_FILE"
+ fi
+
+ print_info "Updated ERPNEXT_IMAGE in $ENV_FILE to:"
+ print_info "$CUSTOM_IMAGE_FULL" print_info "To use this image, it's already tagged as: $BASE_IMAGE"
+ print_info "Your existing docker-compose or deployment will automatically use the custom image!"
+
+ # Update the cache with the new hash
+ echo "$CURRENT_HASH" >"$CACHE_FILE"
+ print_info "Cache updated."
+else
+ print_error "Build failed!"
+ exit 1
+fi
diff --git a/imageroot/actions/configure-module/Dockerfile b/imageroot/actions/configure-module/Dockerfile
new file mode 100644
index 0000000..54d1650
--- /dev/null
+++ b/imageroot/actions/configure-module/Dockerfile
@@ -0,0 +1,60 @@
+ARG FRAPPE_BRANCH=version-15
+
+FROM docker.io/frappe/build:${FRAPPE_BRANCH} AS builder
+
+ARG FRAPPE_BRANCH=version-15
+ARG FRAPPE_PATH=https://github.com/frappe/frappe
+ARG APPS_JSON_BASE64
+
+USER root
+
+RUN if [ -n "${APPS_JSON_BASE64}" ]; then \
+ mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \
+ fi
+
+USER frappe
+
+RUN yarn config set registry https://registry.npmjs.org/ && \
+ yarn config set network-timeout 600000
+RUN export APP_INSTALL_ARGS="" && \
+ if [ -n "${APPS_JSON_BASE64}" ]; then \
+ export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \
+ fi && \
+ bench init ${APP_INSTALL_ARGS}\
+ --frappe-branch=${FRAPPE_BRANCH} \
+ --frappe-path=${FRAPPE_PATH} \
+ --no-procfile \
+ --no-backups \
+ --skip-redis-config-generation \
+ --verbose \
+ /home/frappe/frappe-bench && \
+ cd /home/frappe/frappe-bench && \
+ echo "{}" > sites/common_site_config.json && \
+ find apps -mindepth 1 -path "*/.git" | xargs rm -fr
+
+FROM docker.io/frappe/base:${FRAPPE_BRANCH} AS backend
+
+USER frappe
+
+COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench
+
+WORKDIR /home/frappe/frappe-bench
+
+VOLUME [ \
+ "/home/frappe/frappe-bench/sites", \
+ "/home/frappe/frappe-bench/sites/assets", \
+ "/home/frappe/frappe-bench/logs" \
+ ]
+
+CMD [ \
+ "/home/frappe/frappe-bench/env/bin/gunicorn", \
+ "--chdir=/home/frappe/frappe-bench/sites", \
+ "--bind=0.0.0.0:8000", \
+ "--threads=4", \
+ "--workers=2", \
+ "--worker-class=gthread", \
+ "--worker-tmp-dir=/dev/shm", \
+ "--timeout=120", \
+ "--preload", \
+ "frappe.app:application" \
+ ]
diff --git a/imageroot/actions/configure-module/validate-input.json b/imageroot/actions/configure-module/validate-input.json
index 575ba9e..669b11a 100644
--- a/imageroot/actions/configure-module/validate-input.json
+++ b/imageroot/actions/configure-module/validate-input.json
@@ -7,14 +7,16 @@
{
"host": "erpnext.domain.org",
"http2https": true,
- "lets_encrypt": true
+ "lets_encrypt": true,
+ "appJson": "W3sidXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2ZyYXBwZS9lcnBuZXh0IiwiYnJhbmNoIjoidmVyc2lvbi0xNSIsImFwcF9uYW1lIjoiZXJwbmV4dCJ9XQ=="
}
],
"type": "object",
"required": [
"host",
"http2https",
- "lets_encrypt"
+ "lets_encrypt",
+ "appJson"
],
"properties": {
"host": {
@@ -31,6 +33,17 @@
"type": "boolean",
"title": "HTTP to HTTPS redirection",
"description": "Redirect all the HTTP requests to HTTPS"
+ },
+ "appJson": {
+ "type": "string",
+ "title": "App JSON configuration",
+ "description": "Base64 encoded JSON array containing app configurations with url, branch, and app_name"
+ },
+ "frappeVersion": {
+ "type": "string",
+ "title": "Frappe version",
+ "description": "Frappe version branch to use (e.g., version-15 or version-16)",
+ "enum": ["version-15", "version-16"]
}
}
}
\ No newline at end of file
diff --git a/imageroot/actions/get-configuration/20read b/imageroot/actions/get-configuration/20read
index 295294c..e6fadb8 100755
--- a/imageroot/actions/get-configuration/20read
+++ b/imageroot/actions/get-configuration/20read
@@ -25,13 +25,20 @@ config["lets_encrypt"] = os.getenv("TRAEFIK_LETS_ENCRYPT") == "True"
# Load erpnext-modules.env file
if os.path.exists("erpnext-modules.env"):
data = agent.read_envfile("erpnext-modules.env")
- config["erpSelectedModules"] = data.get("ERP_NEXT_MODULES", [])
+ modules_str = data.get("ERP_NEXT_MODULES", "")
+ config["erpSelectedModules"] = modules_str.split(",") if modules_str else []
+ config["appJson"] = data.get("APPS_JSON", "")
+ config["frappeVersion"] = data.get("FRAPPE_VERSION", "version-15")
else:
config["erpSelectedModules"] = []
+ config["appJson"] = ""
+ config["frappeVersion"] = "version-15"
if os.path.exists("backup_paths.env"):
data = agent.read_envfile("backup_paths.env")
config["hasBackup"] = True
+else:
+ config["hasBackup"] = False
# Dump the configuration to stdout
diff --git a/imageroot/actions/get-configuration/validate-output.json b/imageroot/actions/get-configuration/validate-output.json
index 80368ff..9b9a228 100644
--- a/imageroot/actions/get-configuration/validate-output.json
+++ b/imageroot/actions/get-configuration/validate-output.json
@@ -31,6 +31,26 @@
"type": "boolean",
"title": "HTTP to HTTPS redirection",
"description": "Redirect all the HTTP requests to HTTPS"
+ },
+ "erpSelectedModules": {
+ "type": "array",
+ "title": "ERP Next selected modules",
+ "description": "List of selected ERP Next modules"
+ },
+ "appJson": {
+ "type": "string",
+ "title": "App JSON configuration",
+ "description": "Base64 encoded JSON array containing app configurations"
+ },
+ "frappeVersion": {
+ "type": "string",
+ "title": "Frappe version",
+ "description": "Frappe version branch to use (e.g., version-15 or version-16)"
+ },
+ "hasBackup": {
+ "type": "boolean",
+ "title": "Has backup",
+ "description": "Whether a backup exists for this instance"
}
}
}
\ No newline at end of file
diff --git a/imageroot/actions/podman-images-module/10podman-cm b/imageroot/actions/podman-images-module/10podman-cm
new file mode 100755
index 0000000..4d35eeb
--- /dev/null
+++ b/imageroot/actions/podman-images-module/10podman-cm
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (C) 2022 Nethesis S.r.l.
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+#
+# Get podman images
+#
+
+import os
+import sys
+import json
+import subprocess
+import agent
+
+# Prepare return variable
+images_data = {}
+
+try:
+ # Run podman images command to get images in JSON format
+ result = subprocess.run(
+ ["podman", "images", "--format", "json"],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ images = json.loads(result.stdout)
+ images_data["images"] = images
+ images_data["error"] = None
+except subprocess.CalledProcessError as e:
+ images_data["images"] = []
+ images_data["error"] = f"Failed to get podman images: {e.stderr}"
+except json.JSONDecodeError as e:
+ images_data["images"] = []
+ images_data["error"] = f"Failed to parse podman images output: {str(e)}"
+except Exception as e:
+ images_data["images"] = []
+ images_data["error"] = f"Unexpected error: {str(e)}"
+
+# Dump the images data to stdout
+json.dump(images_data, fp=sys.stdout)
diff --git a/imageroot/actions/podman-images-module/validate-output.json b/imageroot/actions/podman-images-module/validate-output.json
new file mode 100644
index 0000000..7096cd1
--- /dev/null
+++ b/imageroot/actions/podman-images-module/validate-output.json
@@ -0,0 +1,65 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Get podman images",
+ "$id": "http://nethserver.org/json-schema/task/input/erpnext/podman-images",
+ "description": "Get list of podman images",
+ "examples": [
+ {
+ "images": [
+ {
+ "id": "sha256:abc123",
+ "repositories": ["docker.io/library/nginx:latest"],
+ "tags": ["latest"],
+ "created": "2023-01-01 12:00:00 +0000 UTC",
+ "size": "142MB"
+ }
+ ],
+ "error": null
+ }
+ ],
+ "type": "object",
+ "required": ["images", "error"],
+ "properties": {
+ "images": {
+ "type": "array",
+ "title": "Podman images list",
+ "description": "List of podman images with their details",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Image ID"
+ },
+ "repositories": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Image repositories"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Image tags"
+ },
+ "created": {
+ "type": "string",
+ "description": "Creation timestamp"
+ },
+ "size": {
+ "type": "string",
+ "description": "Image size"
+ }
+ }
+ }
+ },
+ "error": {
+ "type": ["string", "null"],
+ "title": "Error message",
+ "description": "Error message if the operation failed, null otherwise"
+ }
+ }
+}
diff --git a/imageroot/bin/create-site b/imageroot/bin/create-site
index e519be8..de3b90e 100755
--- a/imageroot/bin/create-site
+++ b/imageroot/bin/create-site
@@ -36,3 +36,5 @@ else
done
fi
bench --site frontend migrate
+bench clear-cache
+bench clear-website-cache
diff --git a/imageroot/systemd/user/erp-next.service b/imageroot/systemd/user/erp-next.service
index dfbc871..c885b5c 100644
--- a/imageroot/systemd/user/erp-next.service
+++ b/imageroot/systemd/user/erp-next.service
@@ -24,7 +24,9 @@ ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/erp-next.pid \
--pod-id-file %t/erp-next.pod-id \
--name erp-next \
--publish 127.0.0.1:${TCP_PORT}:8080 \
- --replace
+ --replace \
+ --network=slirp4netns:allow_host_loopback=true \
+ --add-host=accountprovider:10.0.2.2
ExecStart=/usr/bin/podman pod start --pod-id-file %t/erp-next.pod-id
ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/erp-next.pod-id -t 10
ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/erp-next.pod-id
diff --git a/ui/public/i18n/de/translation.json b/ui/public/i18n/de/translation.json
index b3e4898..f967146 100644
--- a/ui/public/i18n/de/translation.json
+++ b/ui/public/i18n/de/translation.json
@@ -15,7 +15,18 @@
"test_field": "Testfeld",
"configure_instance": "{instance} konfigurieren",
"save": "Speichern",
- "title": "Einstellungen"
+ "title": "Einstellungen",
+ "erpnext_fqdn": "ERPNext Hostname (FQDN)",
+ "lets_encrypt": "LE Zertifikat anfordern",
+ "http_to_https": "HTTPS erzwingen",
+ "enabled": "Aktiviert",
+ "disabled": "Deaktiviert",
+ "advanced": "Erweitert",
+ "configuring": "Konfiguration",
+ "instance_configuration": "ERPNext konfigurieren",
+ "domain_already_used_in_traefik": "Domain wird bereits in Traefik verwendet",
+ "app_json_must_be_array": "App JSON muss ein Array von Objekten sein",
+ "invalid_json_format": "Ungültiges JSON Format"
},
"common": {
"required": "erforderlich",
@@ -49,9 +60,20 @@
"get-status": "Status abfragen",
"configure-module": "Modul konfigurieren",
"get-configuration": "Konfiguration abrufen",
- "get-name": "Name abrufen"
+ "get-name": "Name abrufen",
+ "build-docker-image": "Docker-Image bauen"
},
"task": {
"cannot_create_task": "{task} kann nicht erstellt werden"
+ },
+ "build": {
+ "title": "Docker-Image bauen",
+ "force_rebuild": "Neubau erzwingen",
+ "no": "Nein",
+ "yes": "Ja",
+ "apps_to_build": "Apps die gebaut werden sollen",
+ "build_image": "Image bauen",
+ "building_image": "Docker-Image wird gebaut",
+ "please_wait": "Bitte warten..."
}
}
diff --git a/ui/public/i18n/en/translation.json b/ui/public/i18n/en/translation.json
index a5af924..dffe19e 100644
--- a/ui/public/i18n/en/translation.json
+++ b/ui/public/i18n/en/translation.json
@@ -21,7 +21,7 @@
"no_volumes": "No volumes",
"webapp": "ERPNEXT Webapp",
"open_webapp": "Open ERPNEXT",
- "not_configured":"ERPNEXT is not configured",
+ "not_configured": "ERPNEXT is not configured",
"configure": "Configure"
},
"settings": {
@@ -36,7 +36,14 @@
"advanced": "Advanced",
"configuring": "Configuring",
"instance_configuration": "Configure ErpNext",
- "domain_already_used_in_traefik": "Domain already used in traefik"
+ "domain_already_used_in_traefik": "Domain already used in traefik",
+ "app_json_must_be_array": "App JSON must be an array of objects",
+ "invalid_json_format": "Invalid JSON format",
+ "frappe_version": "Frappe version",
+ "app_name": "App Name",
+ "repository_url": "Repository URL",
+ "branch": "Branch",
+ "labels": "Labels"
},
"about": {
"title": "About"
@@ -47,6 +54,17 @@
"task": {
"cannot_create_task": "Cannot create task {action}"
},
+ "build": {
+ "title": "Build Docker Image",
+ "force_rebuild": "Force rebuild",
+ "no": "No",
+ "yes": "Yes",
+ "apps_to_build": "Apps to be built",
+ "build_image": "Build Image",
+ "building_image": "Building Docker image",
+ "please_wait": "Please wait...",
+ "frappe_version": "Frappe version"
+ },
"action": {
"get-status": "Get status",
"get-configuration": "Get configuration",
@@ -55,7 +73,8 @@
"get-name": "Get name",
"list-backup-repositories": "List backup repositories",
"list-backups": "List backups",
- "list-installed-modules": "List installed modules"
+ "list-installed-modules": "List installed modules",
+ "build-docker-image": "Build Docker image"
},
"error": {
"error": "Error",
diff --git a/ui/public/i18n/es/translation.json b/ui/public/i18n/es/translation.json
index f2fb14f..e1540f0 100644
--- a/ui/public/i18n/es/translation.json
+++ b/ui/public/i18n/es/translation.json
@@ -15,7 +15,18 @@
"test_field": "Campo de prueba",
"configure_instance": "Configurar {instance}",
"save": "Guardar",
- "title": "Ajustes"
+ "title": "Ajustes",
+ "erpnext_fqdn": "Nombre de host ERPNext (FQDN)",
+ "lets_encrypt": "Solicitar certificado LE",
+ "http_to_https": "Forzar HTTPS",
+ "enabled": "Activado",
+ "disabled": "Desactivado",
+ "advanced": "Avanzado",
+ "configuring": "Configurando",
+ "instance_configuration": "Configurar ERPNext",
+ "domain_already_used_in_traefik": "Dominio ya utilizado en Traefik",
+ "app_json_must_be_array": "App JSON debe ser un array de objetos",
+ "invalid_json_format": "Formato JSON inválido"
},
"common": {
"required": "Obligatorio",
@@ -49,9 +60,20 @@
"get-status": "Obtener el estado",
"configure-module": "Configurar módulo",
"get-configuration": "Obtener la configuración",
- "get-name": "Obtener el nombre"
+ "get-name": "Obtener el nombre",
+ "build-docker-image": "Construir imagen Docker"
},
"task": {
"cannot_create_task": "No se puede crear la tarea {action}"
+ },
+ "build": {
+ "title": "Construir imagen Docker",
+ "force_rebuild": "Forzar reconstrucción",
+ "no": "No",
+ "yes": "Sí",
+ "apps_to_build": "Apps a construir",
+ "build_image": "Construir imagen",
+ "building_image": "Construyendo imagen Docker",
+ "please_wait": "Por favor espere..."
}
}
diff --git a/ui/public/i18n/eu/translation.json b/ui/public/i18n/eu/translation.json
index 1bb747c..42555db 100644
--- a/ui/public/i18n/eu/translation.json
+++ b/ui/public/i18n/eu/translation.json
@@ -15,7 +15,18 @@
"test_field": "Proba-eremua",
"configure_instance": "{instance} konfiguratu",
"save": "Gorde",
- "title": "Ezarpenak"
+ "title": "Ezarpenak",
+ "erpnext_fqdn": "ERPNext Hostname (FQDN)",
+ "lets_encrypt": "LE Ziurtagiria eskatu",
+ "http_to_https": "HTTPS behartu",
+ "enabled": "Gaituta",
+ "disabled": "Ezgaituta",
+ "advanced": "Aurreratua",
+ "configuring": "Konfiguratzen",
+ "instance_configuration": "ERPNext konfiguratu",
+ "domain_already_used_in_traefik": "Domeinua Traefiken dagoeneko erabilita",
+ "app_json_must_be_array": "App JSON objektuen array bat izan behar da",
+ "invalid_json_format": "JSON formatu baliogabea"
},
"common": {
"required": "Beharrezkoa",
@@ -49,9 +60,20 @@
"get-status": "Egoera berreskuratu",
"configure-module": "Modulua konfiguratu",
"get-configuration": "Konfigurazioa berreskuratu",
- "get-name": "Izena berreskuratu"
+ "get-name": "Izena berreskuratu",
+ "build-docker-image": "Docker irudia eraiki"
},
"task": {
"cannot_create_task": "Ezin da zeregina {action} sortu"
+ },
+ "build": {
+ "title": "Docker irudia eraiki",
+ "force_rebuild": "Birsortu behartu",
+ "no": "Ez",
+ "yes": "Bai",
+ "apps_to_build": "Eraiki beharreko app-ak",
+ "build_image": "Irudia eraiki",
+ "building_image": "Docker irudia eraikitzen",
+ "please_wait": "Mesedez itxaron..."
}
}
diff --git a/ui/public/i18n/it/translation.json b/ui/public/i18n/it/translation.json
index bac7a02..8a37ac7 100644
--- a/ui/public/i18n/it/translation.json
+++ b/ui/public/i18n/it/translation.json
@@ -24,7 +24,18 @@
"save": "Salva",
"title": "Impostazioni",
"configure_instance": "Configura {instance}",
- "test_field": "Campo di test"
+ "test_field": "Campo di test",
+ "erpnext_fqdn": "Hostname ERPNext (FQDN)",
+ "lets_encrypt": "Richiedi certificato LE",
+ "http_to_https": "Forza HTTPS",
+ "enabled": "Attivato",
+ "disabled": "Disattivato",
+ "advanced": "Avanzato",
+ "configuring": "Configurazione",
+ "instance_configuration": "Configura ERPNext",
+ "domain_already_used_in_traefik": "Dominio già utilizzato in Traefik",
+ "app_json_must_be_array": "App JSON deve essere un array di oggetti",
+ "invalid_json_format": "Formato JSON non valido"
},
"error": {
"error": "Errore",
@@ -46,11 +57,22 @@
"get-name": "Visualizza nome",
"list-backup-repositories": "Elenca repository di backup",
"list-backups": "Elenca backup",
- "list-installed-modules": "Elenco moduli installati"
+ "list-installed-modules": "Elenco moduli installati",
+ "build-docker-image": "Compila immagine Docker"
},
"task": {
"cannot_create_task": "Impossibile eseguire task {action}"
},
+ "build": {
+ "title": "Compila immagine Docker",
+ "force_rebuild": "Forza ricompilazione",
+ "no": "No",
+ "yes": "Sì",
+ "apps_to_build": "App da compilare",
+ "build_image": "Compila immagine",
+ "building_image": "Compilazione immagine Docker",
+ "please_wait": "Attendere prego..."
+ },
"about": {
"title": "Informazioni"
}
diff --git a/ui/public/i18n/pt/translation.json b/ui/public/i18n/pt/translation.json
index 67db8e5..6e19a2c 100644
--- a/ui/public/i18n/pt/translation.json
+++ b/ui/public/i18n/pt/translation.json
@@ -15,7 +15,18 @@
"test_field": "Campo de teste",
"configure_instance": "Configurar {instância}",
"save": "Gravar",
- "title": "Configurações"
+ "title": "Configurações",
+ "erpnext_fqdn": "Hostname ERPNext (FQDN)",
+ "lets_encrypt": "Solicitar certificado LE",
+ "http_to_https": "Forçar HTTPS",
+ "enabled": "Ativado",
+ "disabled": "Desativado",
+ "advanced": "Avançado",
+ "configuring": "Configurando",
+ "instance_configuration": "Configurar ERPNext",
+ "domain_already_used_in_traefik": "Domínio já utilizado no Traefik",
+ "app_json_must_be_array": "App JSON deve ser uma matriz de objetos",
+ "invalid_json_format": "Formato JSON inválido"
},
"common": {
"required": "Obrigatório",
@@ -49,9 +60,20 @@
"get-status": "Obter status",
"configure-module": "Configurar módulo",
"get-configuration": "Obter configuração",
- "get-name": "Obter nome"
+ "get-name": "Obter nome",
+ "build-docker-image": "Construir imagem Docker"
},
"task": {
"cannot_create_task": "Não é possível criar a tarefa {action}"
+ },
+ "build": {
+ "title": "Construir imagem Docker",
+ "force_rebuild": "Forçar reconstrução",
+ "no": "Não",
+ "yes": "Sim",
+ "apps_to_build": "Apps a construir",
+ "build_image": "Construir imagem",
+ "building_image": "A construir imagem Docker",
+ "please_wait": "Por favor aguarde..."
}
}
diff --git a/ui/public/i18n/pt_BR/translation.json b/ui/public/i18n/pt_BR/translation.json
index 58daa7d..a8c1b6d 100644
--- a/ui/public/i18n/pt_BR/translation.json
+++ b/ui/public/i18n/pt_BR/translation.json
@@ -23,13 +23,37 @@
"title": "About"
},
"action": {
- "list-backup-repositories": "List backup repositories",
- "list-backups": "List backups"
+ "list-backup-repositories": "Listar repositórios de backup",
+ "list-backups": "Listar backups",
+ "build-docker-image": "Compilar imagem Docker"
},
"task": {
"cannot_create_task": "Cannot create task {action}"
},
"settings": {
- "title": "Settings"
+ "title": "Configurações",
+ "save": "Salvar",
+ "configure_instance": "Configurar {instance}",
+ "erpnext_fqdn": "Hostname ERPNext (FQDN)",
+ "lets_encrypt": "Solicitar certificado LE",
+ "http_to_https": "Forçar HTTPS",
+ "enabled": "Ativado",
+ "disabled": "Desativado",
+ "advanced": "Avançado",
+ "configuring": "Configurando",
+ "instance_configuration": "Configurar ERPNext",
+ "domain_already_used_in_traefik": "Domínio já utilizado no Traefik",
+ "app_json_must_be_array": "App JSON deve ser uma matriz de objetos",
+ "invalid_json_format": "Formato JSON inválido"
+ },
+ "build": {
+ "title": "Compilar imagem Docker",
+ "force_rebuild": "Forçar recompilação",
+ "no": "Não",
+ "yes": "Sim",
+ "apps_to_build": "Apps para compilar",
+ "build_image": "Compilar imagem",
+ "building_image": "Compilando imagem Docker",
+ "please_wait": "Por favor aguarde..."
}
}
diff --git a/ui/src/components/AppSideMenuContent.vue b/ui/src/components/AppSideMenuContent.vue
index 87e1201..fd14f85 100644
--- a/ui/src/components/AppSideMenuContent.vue
+++ b/ui/src/components/AppSideMenuContent.vue
@@ -36,12 +36,19 @@
{{ $t("about.title") }}
No apps added yet{{ $t("build.title") }}
+ Manage Apps
+
No podman images found
+