Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 950f4bb58c | |||
| dbbacc98a7 | |||
| 6b42fefe09 | |||
| a3dbd1f89a | |||
| 3742477aef | |||
| bb8e4a258a | |||
| e6d646011a | |||
| 726291995c |
@@ -22,7 +22,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ jobs:
|
|||||||
php ${MOKO_CLI}/branch_rename.php \
|
php ${MOKO_CLI}/branch_rename.php \
|
||||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||||
--pr "${{ github.event.pull_request.number }}"
|
--pr "${{ github.event.pull_request.number }}"
|
||||||
|
|
||||||
- name: Checkout rc and configure git
|
- name: Checkout rc and configure git
|
||||||
@@ -121,7 +121,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update RC release notes from CHANGELOG.md
|
- name: Update RC release notes from CHANGELOG.md
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog
|
# Extract [Unreleased] section from changelog
|
||||||
@@ -269,7 +269,7 @@ jobs:
|
|||||||
!startsWith(steps.platform.outputs.platform, 'joomla')
|
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
SEMVER_TAG="v${VERSION}"
|
SEMVER_TAG="v${VERSION}"
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update release notes and promote changelog
|
- name: Update release notes and promote changelog
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
# Get the stable release info (version and ID)
|
# Get the stable release info (version and ID)
|
||||||
@@ -363,7 +363,7 @@ jobs:
|
|||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/release_mirror.php \
|
php ${MOKO_CLI}/release_mirror.php \
|
||||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
@@ -392,7 +392,7 @@ jobs:
|
|||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
# Delete rc branch (ephemeral — created by promote-rc)
|
# Delete rc branch (ephemeral — created by promote-rc)
|
||||||
@@ -416,7 +416,7 @@ jobs:
|
|||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
BRANCH_NAME="version/${VERSION}"
|
BRANCH_NAME="version/${VERSION}"
|
||||||
@@ -437,7 +437,7 @@ jobs:
|
|||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/version_reset_dev.php \
|
php ${MOKO_CLI}/version_reset_dev.php \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||||
--branch dev --path . 2>&1 || true
|
--branch dev --path . 2>&1 || true
|
||||||
@@ -463,5 +463,5 @@ jobs:
|
|||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: mokocli.Universal
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
|
||||||
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
|
|
||||||
# VERSION: 01.00.00
|
|
||||||
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
|
|
||||||
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
|
||||||
|
|
||||||
name: "Universal: CI Issue Reporter"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
gate:
|
|
||||||
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
details:
|
|
||||||
description: "Human-readable failure description"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
severity:
|
|
||||||
description: "error or warning"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: "error"
|
|
||||||
workflow:
|
|
||||||
description: "Workflow name for the issue title"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
secrets:
|
|
||||||
MOKOGITEA_TOKEN:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
report:
|
|
||||||
name: "Report: ${{ inputs.gate }}"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone MokoCLI
|
|
||||||
env:
|
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
run: |
|
|
||||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
|
||||||
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
|
|
||||||
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
|
||||||
|
|
||||||
- name: Report CI failure
|
|
||||||
env:
|
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
run: |
|
|
||||||
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
|
||||||
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
|
||||||
--gate "${{ inputs.gate }}" \
|
|
||||||
--details "${{ inputs.details }}" \
|
|
||||||
--severity "${{ inputs.severity }}" \
|
|
||||||
--workflow "${{ inputs.workflow }}"
|
|
||||||
@@ -21,7 +21,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -33,17 +33,17 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.GA_TOKEN }}
|
||||||
|
|
||||||
- name: Delete merged branches
|
- name: Delete merged branches
|
||||||
env:
|
env:
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Merged Branch Cleanup ==="
|
echo "=== Merged Branch Cleanup ==="
|
||||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
# List branches via API
|
# List branches via API
|
||||||
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
# Check if branch is merged into main
|
# Check if branch is merged into main
|
||||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||||
echo " Deleting merged branch: ${BRANCH}"
|
echo " Deleting merged branch: ${BRANCH}"
|
||||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
fi
|
fi
|
||||||
@@ -66,20 +66,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Clean old workflow runs
|
- name: Clean old workflow runs
|
||||||
env:
|
env:
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Workflow Run Cleanup ==="
|
echo "=== Workflow Run Cleanup ==="
|
||||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
|
||||||
# Get old completed runs
|
# Get old completed runs
|
||||||
RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/actions/runs?status=completed&limit=50" | \
|
"${API}/actions/runs?status=completed&limit=50" | \
|
||||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
for RUN_ID in $RUNS; do
|
for RUN_ID in $RUNS; do
|
||||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: MokoStandards.Deploy
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
|
|
||||||
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
|
|
||||||
# VERSION: 04.07.00
|
|
||||||
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
|
|
||||||
|
|
||||||
name: "Universal: Deploy to Dev (Manual)"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
clear_remote:
|
|
||||||
description: 'Delete all remote files before uploading'
|
|
||||||
required: false
|
|
||||||
default: 'false'
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: SFTP Deploy to Dev
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
run: |
|
|
||||||
php -v && composer --version
|
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
|
||||||
env:
|
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
|
||||||
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
|
|
||||||
run: |
|
|
||||||
git clone --depth 1 --branch main --quiet \
|
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
|
||||||
/tmp/mokostandards-api 2>/dev/null || true
|
|
||||||
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
|
||||||
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check FTP configuration
|
|
||||||
id: check
|
|
||||||
env:
|
|
||||||
HOST: ${{ vars.DEV_FTP_HOST }}
|
|
||||||
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
|
|
||||||
PORT: ${{ vars.DEV_FTP_PORT }}
|
|
||||||
run: |
|
|
||||||
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
|
|
||||||
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "host=$HOST" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
REMOTE="${PATH_VAR%/}"
|
|
||||||
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
[ -z "$PORT" ] && PORT="22"
|
|
||||||
echo "port=$PORT" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Deploy via SFTP
|
|
||||||
if: steps.check.outputs.skip != 'true'
|
|
||||||
env:
|
|
||||||
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
|
||||||
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
|
||||||
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
|
||||||
run: |
|
|
||||||
SOURCE_DIR="src"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
|
|
||||||
|
|
||||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
|
||||||
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
|
|
||||||
> /tmp/sftp-config.json
|
|
||||||
|
|
||||||
if [ -n "$SFTP_KEY" ]; then
|
|
||||||
echo "$SFTP_KEY" > /tmp/deploy_key
|
|
||||||
chmod 600 /tmp/deploy_key
|
|
||||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
|
||||||
else
|
|
||||||
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
|
||||||
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
|
|
||||||
|
|
||||||
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
|
||||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
|
||||||
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
|
||||||
else
|
|
||||||
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
|
|
||||||
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.43.07
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
@@ -19,7 +19,7 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-branch:
|
create-branch:
|
||||||
@@ -28,8 +28,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Create branch and comment
|
- name: Create branch and comment
|
||||||
run: |
|
run: |
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
echo "Created branch: ${BRANCH}"
|
echo "Created branch: ${BRANCH}"
|
||||||
|
|
||||||
# Comment on issue with branch link
|
# Comment on issue with branch link
|
||||||
REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
|
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
||||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||||
|
|
||||||
curl -sf -X POST \
|
curl -sf -X POST \
|
||||||
|
|||||||
@@ -496,26 +496,39 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Trigger RC pre-release
|
- name: Trigger RC pre-release
|
||||||
env:
|
env:
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
BRANCH: ${{ github.head_ref }}
|
BRANCH: ${{ github.head_ref }}
|
||||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
run: |
|
run: |
|
||||||
curl -s -X POST "${MOKOGITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# ── Issue Reporter ──────────────────────────────────────────────────────
|
# ── Issue Reporter ──────────────────────────────────────────────────────
|
||||||
report-issues:
|
report-issues:
|
||||||
name: Report Issues
|
name: Report Issues
|
||||||
|
runs-on: ubuntu-latest
|
||||||
needs: [branch-policy, validate]
|
needs: [branch-policy, validate]
|
||||||
if: >-
|
if: >-
|
||||||
always() &&
|
always() &&
|
||||||
needs.validate.result == 'failure'
|
needs.validate.result == 'failure'
|
||||||
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
gate: "PR Validation"
|
sparse-checkout: automation/ci-issue-reporter.sh
|
||||||
workflow: "PR Check"
|
sparse-checkout-cone-mode: false
|
||||||
severity: error
|
|
||||||
details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
- name: "File issue for PR validation failure"
|
||||||
secrets: inherit
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
run: |
|
||||||
|
chmod +x automation/ci-issue-reporter.sh
|
||||||
|
./automation/ci-issue-reporter.sh \
|
||||||
|
--gate "PR Validation" \
|
||||||
|
--workflow "PR Check" \
|
||||||
|
--severity error \
|
||||||
|
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/release_create.php \
|
php ${MOKO_CLI}/release_create.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
@@ -193,7 +193,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
@@ -230,7 +230,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/release_package.php \
|
php ${MOKO_CLI}/release_package.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
@@ -243,7 +243,7 @@ jobs:
|
|||||||
if: steps.eligibility.outputs.proceed == 'true'
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
php ${MOKO_CLI}/release_cascade.php \
|
php ${MOKO_CLI}/release_cascade.php \
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- name: Check actor permission (admin only)
|
- name: Check actor permission (admin only)
|
||||||
id: perm
|
id: perm
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
ACTOR: ${{ github.actor }}
|
ACTOR: ${{ github.actor }}
|
||||||
run: |
|
run: |
|
||||||
@@ -671,30 +671,42 @@ jobs:
|
|||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
# Issue Reporter — file issues for failed gates
|
# Issue Reporter — file issues for failed gates
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
report-scripts:
|
report-issues:
|
||||||
name: "Report: Scripts Governance"
|
name: "Report Issues"
|
||||||
needs: [access_check, scripts_governance]
|
runs-on: ubuntu-latest
|
||||||
|
needs: [access_check, scripts_governance, repo_health]
|
||||||
if: >-
|
if: >-
|
||||||
always() &&
|
always() &&
|
||||||
needs.scripts_governance.result == 'failure'
|
(needs.scripts_governance.result == 'failure' ||
|
||||||
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
|
needs.repo_health.result == 'failure')
|
||||||
with:
|
|
||||||
gate: "Scripts Governance"
|
|
||||||
workflow: "Repo Health"
|
|
||||||
severity: error
|
|
||||||
details: "Scripts directory policy violations detected. Review required and allowed directories."
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
report-health:
|
steps:
|
||||||
name: "Report: Repository Health"
|
- name: Checkout
|
||||||
needs: [access_check, repo_health]
|
uses: actions/checkout@v4
|
||||||
if: >-
|
|
||||||
always() &&
|
|
||||||
needs.repo_health.result == 'failure'
|
|
||||||
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
|
|
||||||
with:
|
with:
|
||||||
gate: "Repository Health"
|
sparse-checkout: automation/ci-issue-reporter.sh
|
||||||
workflow: "Repo Health"
|
sparse-checkout-cone-mode: false
|
||||||
severity: error
|
|
||||||
details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
|
- name: "File issues for failed gates"
|
||||||
secrets: inherit
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
run: |
|
||||||
|
chmod +x automation/ci-issue-reporter.sh
|
||||||
|
REPORTER="./automation/ci-issue-reporter.sh"
|
||||||
|
WF="Repo Health"
|
||||||
|
|
||||||
|
report_gate() {
|
||||||
|
local gate="$1" result="$2" details="$3"
|
||||||
|
if [ "$result" = "failure" ]; then
|
||||||
|
"$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
report_gate "Scripts Governance" \
|
||||||
|
"${{ needs.scripts_governance.result }}" \
|
||||||
|
"Scripts directory policy violations detected. Review required and allowed directories."
|
||||||
|
|
||||||
|
report_gate "Repository Health" \
|
||||||
|
"${{ needs.repo_health.result }}" \
|
||||||
|
"Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow.Template
|
|
||||||
# INGROUP: MokoStandards.CI
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
|
||||||
# PATH: /.mokogitea/workflows/version-set.yml
|
|
||||||
# VERSION: 01.00.00
|
|
||||||
# BRIEF: Set or reset the extension version across all version-bearing files
|
|
||||||
|
|
||||||
name: "Joomla: Set Version"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: "Version number (e.g. 01.00.00)"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
branch:
|
|
||||||
description: "Branch to update (default: current)"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
set-version:
|
|
||||||
name: Set Version to ${{ inputs.version }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Validate version format
|
|
||||||
run: |
|
|
||||||
VERSION="${{ inputs.version }}"
|
|
||||||
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
|
|
||||||
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
|
||||||
ref: ${{ inputs.branch || github.ref }}
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Update manifest version
|
|
||||||
run: |
|
|
||||||
MANIFEST=""
|
|
||||||
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
|
||||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
|
||||||
MANIFEST="$XML_FILE"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$MANIFEST" ]; then
|
|
||||||
echo "::warning::No Joomla extension manifest found — skipping manifest update"
|
|
||||||
else
|
|
||||||
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
|
|
||||||
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
|
|
||||||
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Update README.md version
|
|
||||||
run: |
|
|
||||||
if [ -f "README.md" ]; then
|
|
||||||
if grep -qP '^\s*VERSION:\s*\d' README.md; then
|
|
||||||
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
|
|
||||||
echo "README.md version updated to ${VERSION}"
|
|
||||||
else
|
|
||||||
echo "::warning::No VERSION line found in README.md — skipping"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Update CHANGELOG.md
|
|
||||||
run: |
|
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
|
||||||
DATE=$(date +%Y-%m-%d)
|
|
||||||
# Check if this version already has an entry
|
|
||||||
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
|
|
||||||
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
|
|
||||||
else
|
|
||||||
# Insert new version entry after [Unreleased] or at the top after header
|
|
||||||
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
|
|
||||||
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
|
||||||
else
|
|
||||||
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
|
||||||
fi
|
|
||||||
echo "CHANGELOG.md: added entry for ${VERSION}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "::warning::No CHANGELOG.md found — skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Update FILE INFORMATION blocks
|
|
||||||
run: |
|
|
||||||
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
|
|
||||||
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
|
|
||||||
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
|
|
||||||
while IFS= read -r -d '' FILE; do
|
|
||||||
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
|
|
||||||
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
|
|
||||||
echo "Updated FILE INFORMATION VERSION in ${FILE}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Commit and push
|
|
||||||
run: |
|
|
||||||
git config user.name "Moko Consulting [bot]"
|
|
||||||
git config user.email "hello@mokoconsulting.tech"
|
|
||||||
git add -A
|
|
||||||
if git diff --cached --quiet; then
|
|
||||||
echo "No version changes detected — nothing to commit"
|
|
||||||
else
|
|
||||||
git commit -m "chore: set version to ${VERSION} [skip bump]
|
|
||||||
|
|
||||||
Authored-by: Moko Consulting"
|
|
||||||
git push
|
|
||||||
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
name: "Universal: Workflow Sync Trigger"
|
name: "Universal: Workflow Sync Trigger"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types: [closed]
|
||||||
branches:
|
branches:
|
||||||
@@ -27,9 +26,8 @@ jobs:
|
|||||||
name: Sync workflows to live repos
|
name: Sync workflows to live repos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: >-
|
if: >-
|
||||||
github.event_name == 'workflow_dispatch' ||
|
github.event.pull_request.merged == true &&
|
||||||
(github.event.pull_request.merged == true &&
|
!contains(github.event.pull_request.title, '[skip sync]')
|
||||||
!contains(github.event.pull_request.title, '[skip sync]'))
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Determine platform from repo name
|
- name: Determine platform from repo name
|
||||||
@@ -51,14 +49,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||||
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||||
|
|
||||||
- name: Install PHP
|
|
||||||
run: |
|
|
||||||
if ! command -v php &> /dev/null; then
|
|
||||||
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -245,7 +245,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
label="COM_MOKOJOOMBACKUP_CONFIG_NTFY_SERVER"
|
label="COM_MOKOJOOMBACKUP_CONFIG_NTFY_SERVER"
|
||||||
description="COM_MOKOJOOMBACKUP_CONFIG_NTFY_SERVER_DESC"
|
description="COM_MOKOJOOMBACKUP_CONFIG_NTFY_SERVER_DESC"
|
||||||
default="https://ntfy.sh"
|
default="https://ntfy.mokoconsulting.tech"
|
||||||
filter="url"
|
filter="url"
|
||||||
/>
|
/>
|
||||||
<field
|
<field
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
default="a.ordering ASC"
|
default="a.ordering ASC"
|
||||||
onchange="this.form.submit();"
|
onchange="this.form.submit();"
|
||||||
>
|
>
|
||||||
<option value="a.ordering ASC">JFIELD_ORDERING_LABEL_ASC</option>
|
<option value="a.ordering ASC">JFIELD_ORDERING_ASC</option>
|
||||||
<option value="a.title ASC">COM_MOKOJOOMBACKUP_HEADING_TITLE_ASC</option>
|
<option value="a.title ASC">COM_MOKOJOOMBACKUP_HEADING_TITLE_ASC</option>
|
||||||
<option value="a.title DESC">COM_MOKOJOOMBACKUP_HEADING_TITLE_DESC</option>
|
<option value="a.title DESC">COM_MOKOJOOMBACKUP_HEADING_TITLE_DESC</option>
|
||||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
; @license GPL-3.0-or-later
|
; @license GPL-3.0-or-later
|
||||||
|
|
||||||
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
|
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
|
||||||
|
COM_MOKOJOOMBACKUP_CONFIGURATION="MokoSuiteBackup Options"
|
||||||
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla"
|
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla"
|
||||||
|
|
||||||
; Submenu
|
; Submenu
|
||||||
@@ -275,9 +276,9 @@ COM_MOKOJOOMBACKUP_FIELD_SFTP_PORT_DESC="SSH port (default: 22)"
|
|||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME="SSH Username"
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME="SSH Username"
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME_DESC="Username for SSH authentication"
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME_DESC="Username for SSH authentication"
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD="SSH Password"
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD="SSH Password"
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD_DESC="Password for SSH authentication. Leave blank if using a key file."
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD_DESC="Password for SSH authentication."
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY="SSH Private Key"
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY="SSH Private Key"
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Upload your SSH private key (id_rsa, id_ed25519). Stored base64-encoded in DB, written to temp file during upload only. Leave blank for password auth."
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Upload your SSH private key (id_rsa, id_ed25519). Stored base64-encoded in DB, written to temp file during upload only."
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_UPLOAD="Upload Key File"
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_UPLOAD="Upload Key File"
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_REPLACE="Replace Key"
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_REPLACE="Replace Key"
|
||||||
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED="Key loaded"
|
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED="Key loaded"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="component" method="upgrade">
|
<extension type="component" method="upgrade">
|
||||||
<name>MokoSuiteBackup</name>
|
<name>MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -165,7 +165,38 @@ SCANNER;
|
|||||||
$php
|
$php
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Modify the pre-checks to use getSelectedBackupFile() */
|
/* Replace the backup archive check with one that scans for ZIPs
|
||||||
|
(must run BEFORE the blanket file_exists replacement below) */
|
||||||
|
$php = str_replace(
|
||||||
|
<<<'ORIG'
|
||||||
|
$checks[] = [
|
||||||
|
'label' => 'Backup Archive',
|
||||||
|
'value' => file_exists(BACKUP_FILE) ? number_format(filesize(BACKUP_FILE) / 1048576, 2) . ' MB' : 'Not found',
|
||||||
|
'ok' => file_exists(BACKUP_FILE),
|
||||||
|
'hint' => 'site-backup.zip must be in the same directory as restore.php',
|
||||||
|
];
|
||||||
|
ORIG,
|
||||||
|
<<<'REPL'
|
||||||
|
$availableBackups = scanForBackups();
|
||||||
|
$backupCount = count($availableBackups);
|
||||||
|
$selectedFile = getSelectedBackupFile();
|
||||||
|
if ($selectedFile && file_exists($selectedFile)) {
|
||||||
|
$archiveValue = basename($selectedFile) . ' (' . number_format(filesize($selectedFile) / 1048576, 2) . ' MB)';
|
||||||
|
} elseif ($backupCount > 0) {
|
||||||
|
$archiveValue = $backupCount . ' ZIP file(s) found';
|
||||||
|
} else {
|
||||||
|
$archiveValue = 'No ZIP files found';
|
||||||
|
}
|
||||||
|
$checks[] = [
|
||||||
|
'label' => 'Backup Archive',
|
||||||
|
'value' => $archiveValue,
|
||||||
|
'ok' => $backupCount > 0,
|
||||||
|
'hint' => 'Place one or more backup ZIP files in the same directory as restore.php',
|
||||||
|
];
|
||||||
|
REPL
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Modify remaining pre-checks to use getSelectedBackupFile() */
|
||||||
$php = str_replace(
|
$php = str_replace(
|
||||||
"file_exists(BACKUP_FILE)",
|
"file_exists(BACKUP_FILE)",
|
||||||
"(getSelectedBackupFile() !== '' || file_exists(BACKUP_FILE))",
|
"(getSelectedBackupFile() !== '' || file_exists(BACKUP_FILE))",
|
||||||
@@ -174,12 +205,10 @@ SCANNER;
|
|||||||
|
|
||||||
$html = self::generateFrontend();
|
$html = self::generateFrontend();
|
||||||
|
|
||||||
/* Add backup file selector to the frontend before the extract step */
|
/* Inject backup file selector into the extract step (panel2) */
|
||||||
$selectorHtml = <<<'SELECTOR'
|
$selectorHtml = <<<'SELECTOR'
|
||||||
<!-- Backup File Selector (standalone mode) -->
|
<div id="mr-backup-selector" class="mb-3">
|
||||||
<div id="mr-step-select" class="mr-step" style="display:none;">
|
<label class="mr-field-label" style="font-weight:600;margin-bottom:8px;display:block;">Backup Archive</label>
|
||||||
<h2 class="mr-step-title">Select Backup File</h2>
|
|
||||||
<p class="mr-desc">Choose which backup archive to restore from.</p>
|
|
||||||
<div id="mr-backup-list"></div>
|
<div id="mr-backup-list"></div>
|
||||||
<input type="hidden" name="backup_file" id="mr-backup-file" value="">
|
<input type="hidden" name="backup_file" id="mr-backup-file" value="">
|
||||||
</div>
|
</div>
|
||||||
@@ -191,13 +220,13 @@ SCANNER;
|
|||||||
|
|
||||||
if (backups.length === 0) {
|
if (backups.length === 0) {
|
||||||
var alert = document.createElement('div');
|
var alert = document.createElement('div');
|
||||||
alert.className = 'mr-alert mr-alert-danger';
|
alert.style.cssText = 'padding:12px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:#dc2626;';
|
||||||
alert.textContent = 'No ZIP files found in this directory. Upload a backup archive first.';
|
alert.textContent = 'No ZIP files found in this directory. Upload a backup archive first.';
|
||||||
list.appendChild(alert);
|
list.appendChild(alert);
|
||||||
} else if (backups.length === 1) {
|
} else if (backups.length === 1) {
|
||||||
hiddenInput.value = backups[0].name;
|
hiddenInput.value = backups[0].name;
|
||||||
var found = document.createElement('div');
|
var found = document.createElement('div');
|
||||||
found.className = 'mr-alert mr-alert-success';
|
found.style.cssText = 'padding:12px;background:#dcfce7;border:1px solid #bbf7d0;border-radius:6px;color:#16a34a;';
|
||||||
var strong = document.createElement('strong');
|
var strong = document.createElement('strong');
|
||||||
strong.textContent = backups[0].name;
|
strong.textContent = backups[0].name;
|
||||||
found.appendChild(document.createTextNode('Found: '));
|
found.appendChild(document.createTextNode('Found: '));
|
||||||
@@ -205,34 +234,54 @@ SCANNER;
|
|||||||
found.appendChild(document.createTextNode(' (' + (backups[0].size / 1048576).toFixed(1) + ' MB)'));
|
found.appendChild(document.createTextNode(' (' + (backups[0].size / 1048576).toFixed(1) + ' MB)'));
|
||||||
list.appendChild(found);
|
list.appendChild(found);
|
||||||
} else {
|
} else {
|
||||||
var group = document.createElement('div');
|
var hint = document.createElement('div');
|
||||||
group.className = 'mr-field-group';
|
hint.style.cssText = 'padding:8px 12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;color:#1d4ed8;margin-bottom:8px;font-size:0.9em;';
|
||||||
backups.forEach(function(b) {
|
hint.textContent = 'Multiple backup archives found \u2014 select which one to restore:';
|
||||||
|
list.appendChild(hint);
|
||||||
|
backups.forEach(function(b, i) {
|
||||||
var label = document.createElement('label');
|
var label = document.createElement('label');
|
||||||
label.style.cssText = 'display:block; padding:8px; margin:4px 0; border:1px solid #ddd; border-radius:4px; cursor:pointer;';
|
label.style.cssText = 'display:flex;align-items:center;padding:10px 12px;margin:4px 0;border:1px solid #e2e8f0;border-radius:6px;cursor:pointer;transition:background 0.15s;';
|
||||||
|
label.onmouseover = function() { this.style.background = '#f8fafc'; };
|
||||||
|
label.onmouseout = function() { this.style.background = ''; };
|
||||||
var radio = document.createElement('input');
|
var radio = document.createElement('input');
|
||||||
radio.type = 'radio';
|
radio.type = 'radio';
|
||||||
radio.name = 'backup_choice';
|
radio.name = 'backup_choice';
|
||||||
radio.value = b.name;
|
radio.value = b.name;
|
||||||
radio.style.marginRight = '8px';
|
radio.style.marginRight = '10px';
|
||||||
|
if (i === 0) { radio.checked = true; hiddenInput.value = b.name; }
|
||||||
radio.addEventListener('change', function() { hiddenInput.value = this.value; });
|
radio.addEventListener('change', function() { hiddenInput.value = this.value; });
|
||||||
label.appendChild(radio);
|
label.appendChild(radio);
|
||||||
|
var info = document.createElement('div');
|
||||||
var nameStrong = document.createElement('strong');
|
var nameStrong = document.createElement('strong');
|
||||||
nameStrong.textContent = b.name;
|
nameStrong.textContent = b.name;
|
||||||
label.appendChild(nameStrong);
|
info.appendChild(nameStrong);
|
||||||
label.appendChild(document.createTextNode(' \u2014 ' + (b.size / 1048576).toFixed(1) + ' MB \u2014 ' + b.date));
|
var meta = document.createElement('div');
|
||||||
group.appendChild(label);
|
meta.style.cssText = 'font-size:0.85em;color:#64748b;margin-top:2px;';
|
||||||
|
meta.textContent = (b.size / 1048576).toFixed(1) + ' MB \u2014 ' + b.date;
|
||||||
|
info.appendChild(meta);
|
||||||
|
label.appendChild(info);
|
||||||
|
list.appendChild(label);
|
||||||
});
|
});
|
||||||
list.appendChild(group);
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
SELECTOR;
|
SELECTOR;
|
||||||
|
|
||||||
/* Insert the selector before the extract step in the HTML */
|
/* Insert the selector into the extract panel */
|
||||||
$html = str_replace(
|
$html = str_replace(
|
||||||
'<!-- Step: Extract -->',
|
'<p class="mr-desc">Extract site-backup.zip into the current directory.</p>',
|
||||||
$selectorHtml . "\n<!-- Step: Extract -->",
|
'<p class="mr-desc">Select a backup archive and extract it into the current directory.</p>' . "\n" . $selectorHtml,
|
||||||
|
$html
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Pass selected backup file to the extract action */
|
||||||
|
$html = str_replace(
|
||||||
|
"const r = await post('extract', pw ? { archive_password: pw } : {});",
|
||||||
|
"var extraParams = {};\n" .
|
||||||
|
" if (pw) extraParams.archive_password = pw;\n" .
|
||||||
|
" var sel = document.getElementById('mr-backup-file');\n" .
|
||||||
|
" if (sel && sel.value) extraParams.backup_file = sel.value;\n" .
|
||||||
|
" const r = await post('extract', extraParams);",
|
||||||
$html
|
$html
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -462,15 +511,31 @@ function actionPreflight(): array
|
|||||||
'hint' => 'Informational',
|
'hint' => 'Informational',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$joomlaExists = file_exists(RESTORE_DIR . '/configuration.php')
|
||||||
|
|| file_exists(RESTORE_DIR . '/libraries/src/Version.php');
|
||||||
|
$checks[] = [
|
||||||
|
'label' => 'Existing Installation',
|
||||||
|
'value' => $joomlaExists ? 'Joomla detected' : 'Clean directory',
|
||||||
|
'ok' => true,
|
||||||
|
'warn' => $joomlaExists,
|
||||||
|
'hint' => $joomlaExists
|
||||||
|
? 'WARNING: A Joomla installation already exists in this directory. Restoring will overwrite it.'
|
||||||
|
: 'No existing installation found — safe to proceed',
|
||||||
|
];
|
||||||
|
|
||||||
$allOk = true;
|
$allOk = true;
|
||||||
|
$warnings = [];
|
||||||
|
|
||||||
foreach ($checks as $c) {
|
foreach ($checks as $c) {
|
||||||
if (!$c['ok']) {
|
if (!$c['ok']) {
|
||||||
$allOk = false;
|
$allOk = false;
|
||||||
}
|
}
|
||||||
|
if (!empty($c['warn'])) {
|
||||||
|
$warnings[] = $c['hint'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['success' => $allOk, 'checks' => $checks];
|
return ['success' => $allOk, 'checks' => $checks, 'warnings' => $warnings];
|
||||||
}
|
}
|
||||||
|
|
||||||
function actionExtract(array $data): array
|
function actionExtract(array $data): array
|
||||||
@@ -1425,6 +1490,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica N
|
|||||||
.mr-checks li:last-child{border-bottom:none}
|
.mr-checks li:last-child{border-bottom:none}
|
||||||
.mr-check-icon{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;flex-shrink:0}
|
.mr-check-icon{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;flex-shrink:0}
|
||||||
.mr-check-ok{background:#dcfce7;color:#16a34a}
|
.mr-check-ok{background:#dcfce7;color:#16a34a}
|
||||||
|
.mr-check-warn{background:#fef9c3;color:#a16207}
|
||||||
.mr-check-fail{background:#fef2f2;color:#dc2626}
|
.mr-check-fail{background:#fef2f2;color:#dc2626}
|
||||||
.mr-check-info{background:#e0f2fe;color:#0284c7}
|
.mr-check-info{background:#e0f2fe;color:#0284c7}
|
||||||
.mr-check-label{flex:1;font-weight:500}
|
.mr-check-label{flex:1;font-weight:500}
|
||||||
@@ -1769,8 +1835,23 @@ async function post(action, extra) {
|
|||||||
form.append(k, v);
|
form.append(k, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const res = await fetch('restore.php', { method: 'POST', body: form });
|
var res;
|
||||||
return res.json();
|
try {
|
||||||
|
res = await fetch('restore.php', { method: 'POST', body: form });
|
||||||
|
} catch (e) {
|
||||||
|
log('Network error: ' + e.message);
|
||||||
|
return { success: false, message: 'Network error: ' + e.message, checks: [] };
|
||||||
|
}
|
||||||
|
if (!res.ok) {
|
||||||
|
log('Server error: HTTP ' + res.status);
|
||||||
|
return { success: false, message: 'Server error (HTTP ' + res.status + ')', checks: [] };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
log('Invalid response from server (not JSON)');
|
||||||
|
return { success: false, message: 'Invalid server response — check PHP error log', checks: [] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goStep(n) {
|
function goStep(n) {
|
||||||
@@ -1845,15 +1926,27 @@ async function runPreflight() {
|
|||||||
setBtnLoading(btn, true);
|
setBtnLoading(btn, true);
|
||||||
log('Running pre-flight checks...');
|
log('Running pre-flight checks...');
|
||||||
|
|
||||||
|
try {
|
||||||
const r = await post('preflight');
|
const r = await post('preflight');
|
||||||
|
|
||||||
|
if (!r.success && !r.checks.length) {
|
||||||
|
log('Pre-flight error: ' + (r.message || 'Unknown error'));
|
||||||
|
setBtnLoading(btn, false);
|
||||||
|
btn.textContent = 'Re-check';
|
||||||
|
setStatus('checkList', r.message || 'Pre-flight check failed', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const list = document.getElementById('checkList');
|
const list = document.getElementById('checkList');
|
||||||
while (list.firstChild) list.removeChild(list.firstChild);
|
while (list.firstChild) list.removeChild(list.firstChild);
|
||||||
|
|
||||||
r.checks.forEach(function(c) {
|
r.checks.forEach(function(c) {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
const icon = document.createElement('span');
|
const icon = document.createElement('span');
|
||||||
icon.className = 'mr-check-icon ' + (c.ok ? 'mr-check-ok' : 'mr-check-fail');
|
var iconClass = c.ok ? 'mr-check-ok' : 'mr-check-fail';
|
||||||
icon.textContent = c.ok ? '\u2713' : '\u2717';
|
if (c.warn) iconClass = 'mr-check-warn';
|
||||||
|
icon.className = 'mr-check-icon ' + iconClass;
|
||||||
|
icon.textContent = c.warn ? '\u26a0' : (c.ok ? '\u2713' : '\u2717');
|
||||||
|
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.className = 'mr-check-label';
|
label.className = 'mr-check-label';
|
||||||
@@ -1866,9 +1959,16 @@ async function runPreflight() {
|
|||||||
li.appendChild(icon);
|
li.appendChild(icon);
|
||||||
li.appendChild(label);
|
li.appendChild(label);
|
||||||
li.appendChild(val);
|
li.appendChild(val);
|
||||||
|
if (c.warn && c.hint) {
|
||||||
|
var hint = document.createElement('div');
|
||||||
|
hint.style.cssText = 'font-size:0.85em;color:#a16207;margin-top:4px;padding:4px 8px;background:#fef9c3;border-radius:4px;';
|
||||||
|
hint.textContent = c.hint;
|
||||||
|
li.appendChild(hint);
|
||||||
|
}
|
||||||
list.appendChild(li);
|
list.appendChild(li);
|
||||||
|
|
||||||
log(' ' + (c.ok ? 'OK' : 'FAIL') + ': ' + c.label + ' = ' + c.value);
|
var logPrefix = c.warn ? 'WARN' : (c.ok ? 'OK' : 'FAIL');
|
||||||
|
log(' ' + logPrefix + ': ' + c.label + ' = ' + c.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
setBtnLoading(btn, false);
|
setBtnLoading(btn, false);
|
||||||
@@ -1882,6 +1982,11 @@ async function runPreflight() {
|
|||||||
btn.textContent = 'Re-check';
|
btn.textContent = 'Re-check';
|
||||||
log('Some checks failed');
|
log('Some checks failed');
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('Pre-flight error: ' + e.message);
|
||||||
|
setBtnLoading(btn, false);
|
||||||
|
btn.textContent = 'Re-check';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2
|
// Step 2
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
<select id="mb-profile-select" class="form-select" style="max-width:300px;">
|
<select id="mb-profile-select" class="form-select" style="max-width:300px;">
|
||||||
<?php foreach ($this->profiles as $profile) : ?>
|
<?php foreach ($this->profiles as $profile) : ?>
|
||||||
<option value="<?php echo (int) $profile->id; ?>">
|
<option value="<?php echo (int) $profile->id; ?>">
|
||||||
|
#<?php echo (int) $profile->id; ?> —
|
||||||
<?php echo $this->escape($profile->title); ?>
|
<?php echo $this->escape($profile->title); ?>
|
||||||
(<?php echo $this->escape($profile->backup_type); ?>)
|
(<?php echo $this->escape($profile->backup_type); ?>)
|
||||||
</option>
|
</option>
|
||||||
@@ -188,18 +189,24 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Stepped Backup Modal (for shared hosting) -->
|
<!-- Stepped Backup Modal (for shared hosting) -->
|
||||||
<div id="mokosuitebackup-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mokosuitebackup-modal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||||
<div style="max-width:500px; margin:10% auto; background:#fff; border-radius:8px; padding:2rem; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
<div class="modal-dialog">
|
||||||
<h3 id="mb-modal-title" style="margin:0 0 1rem;">Backup in Progress</h3>
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="mb-modal-title">Backup in Progress</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
<div class="alert alert-warning py-1 px-2 mb-2" style="font-size:0.85rem;">
|
<div class="alert alert-warning py-1 px-2 mb-2" style="font-size:0.85rem;">
|
||||||
<span class="icon-warning-circle" aria-hidden="true"></span>
|
<span class="icon-warning-circle" aria-hidden="true"></span>
|
||||||
<strong>Do not navigate away or close this window</strong> while the backup is running.
|
<strong>Do not navigate away or close this window</strong> while the backup is running.
|
||||||
</div>
|
</div>
|
||||||
<div style="background:#e9ecef; border-radius:4px; overflow:hidden; height:24px; margin-bottom:0.5rem;">
|
<div class="progress mb-2" style="height:24px;">
|
||||||
<div id="mb-progress-bar" style="height:100%; background:#0d6efd; transition:width 0.3s; width:0%; display:flex; align-items:center; justify-content:center; color:#fff; font-size:0.8rem; font-weight:bold;">0%</div>
|
<div id="mb-progress-bar" class="progress-bar" role="progressbar" style="width:0%;">0%</div>
|
||||||
|
</div>
|
||||||
|
<p id="mb-status" class="text-muted mb-1" style="font-size:0.9rem;">Initializing...</p>
|
||||||
|
<p id="mb-phase" class="text-muted mb-0" style="font-size:0.8rem;">Phase: init</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p id="mb-status" style="color:#666; font-size:0.9rem; margin:0.5rem 0;">Initializing...</p>
|
|
||||||
<p id="mb-phase" style="color:#999; font-size:0.8rem; margin:0;">Phase: init</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -235,12 +242,12 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
backupRunning = true;
|
backupRunning = true;
|
||||||
document.getElementById('mokosuitebackup-modal').style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('mokosuitebackup-modal')).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideModal() {
|
function hideModal() {
|
||||||
backupRunning = false;
|
backupRunning = false;
|
||||||
document.getElementById('mokosuitebackup-modal').style.display = 'none';
|
bootstrap.Modal.getInstance(document.getElementById('mokosuitebackup-modal'))?.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProgress(progress, message, phase) {
|
function updateProgress(progress, message, phase) {
|
||||||
@@ -344,31 +351,26 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
document.getElementById('mb-restore-record-id').value = checked[0].value;
|
document.getElementById('mb-restore-record-id').value = checked[0].value;
|
||||||
document.getElementById('mb-restore-modal').style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('mb-restore-modal')).show();
|
||||||
return false;
|
return false;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close restore modal
|
// Close restore modal handled by Bootstrap data-bs-dismiss
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
if (e.target.classList.contains('mb-restore-close') || e.target.id === 'mb-restore-modal') {
|
|
||||||
document.getElementById('mb-restore-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// AJAX stepped restore
|
// AJAX stepped restore
|
||||||
var restoreRunning = false;
|
var restoreRunning = false;
|
||||||
|
|
||||||
function showRestoreProgress() {
|
function showRestoreProgress() {
|
||||||
restoreRunning = true;
|
restoreRunning = true;
|
||||||
document.getElementById('mb-restore-modal').style.display = 'none';
|
bootstrap.Modal.getInstance(document.getElementById('mb-restore-modal'))?.hide();
|
||||||
document.getElementById('mb-restore-progress-modal').style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('mb-restore-progress-modal')).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideRestoreProgress() {
|
function hideRestoreProgress() {
|
||||||
restoreRunning = false;
|
restoreRunning = false;
|
||||||
document.getElementById('mb-restore-progress-modal').style.display = 'none';
|
bootstrap.Modal.getInstance(document.getElementById('mb-restore-progress-modal'))?.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRestoreProgress(progress, message, phase) {
|
function updateRestoreProgress(progress, message, phase) {
|
||||||
@@ -466,7 +468,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
var modal = document.getElementById('mb-log-modal');
|
var modal = document.getElementById('mb-log-modal');
|
||||||
var body = document.getElementById('mb-log-body');
|
var body = document.getElementById('mb-log-body');
|
||||||
body.textContent = 'Loading...';
|
body.textContent = 'Loading...';
|
||||||
modal.style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(modal).show();
|
||||||
|
|
||||||
var form = new URLSearchParams();
|
var form = new URLSearchParams();
|
||||||
form.append('task', 'ajax.viewLog');
|
form.append('task', 'ajax.viewLog');
|
||||||
@@ -491,11 +493,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', function(e) {
|
// Log modal close handled by Bootstrap data-bs-dismiss
|
||||||
if (e.target.id === 'mb-log-modal' || e.target.classList.contains('mb-log-close')) {
|
|
||||||
document.getElementById('mb-log-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Browse Archive modal handler
|
// Browse Archive modal handler
|
||||||
function formatFileSize(bytes) {
|
function formatFileSize(bytes) {
|
||||||
@@ -551,7 +549,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
var summary = document.getElementById('mb-browse-summary');
|
var summary = document.getElementById('mb-browse-summary');
|
||||||
browseSetMessage(tbody, 'Loading...');
|
browseSetMessage(tbody, 'Loading...');
|
||||||
summary.textContent = '';
|
summary.textContent = '';
|
||||||
modal.style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(modal).show();
|
||||||
|
|
||||||
postAjax({ task: 'ajax.browseArchive', id: recordId })
|
postAjax({ task: 'ajax.browseArchive', id: recordId })
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
@@ -578,24 +576,21 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', function(e) {
|
// Browse modal close handled by Bootstrap data-bs-dismiss
|
||||||
if (e.target.id === 'mb-browse-modal' || e.target.classList.contains('mb-browse-close')) {
|
|
||||||
document.getElementById('mb-browse-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Restore Confirmation Modal -->
|
<!-- Restore Confirmation Modal -->
|
||||||
<div id="mb-restore-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-restore-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:500px; margin:8% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
<div class="modal-dialog">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;"><?php echo Text::_('COM_MOKOJOOMBACKUP_TOOLBAR_RESTORE'); ?></h4>
|
<div class="modal-header">
|
||||||
<button type="button" class="btn-close mb-restore-close" aria-label="Close"></button>
|
<h5 class="modal-title"><?php echo Text::_('COM_MOKOJOOMBACKUP_TOOLBAR_RESTORE'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=backups.restore'); ?>" method="post" id="mb-restore-form">
|
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=backups.restore'); ?>" method="post" id="mb-restore-form">
|
||||||
<input type="hidden" name="id" id="mb-restore-record-id" value="">
|
<input type="hidden" name="id" id="mb-restore-record-id" value="">
|
||||||
<div style="padding:1.5rem;">
|
<div class="modal-body">
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<span class="icon-warning-circle" aria-hidden="true"></span>
|
<span class="icon-warning-circle" aria-hidden="true"></span>
|
||||||
<strong><?php echo Text::_('COM_MOKOJOOMBACKUP_RESTORE_CONFIRM'); ?></strong>
|
<strong><?php echo Text::_('COM_MOKOJOOMBACKUP_RESTORE_CONFIRM'); ?></strong>
|
||||||
@@ -629,8 +624,8 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
placeholder="<?php echo Text::_('COM_MOKOJOOMBACKUP_RESTORE_PASSWORD_PLACEHOLDER'); ?>" autocomplete="off">
|
placeholder="<?php echo Text::_('COM_MOKOJOOMBACKUP_RESTORE_PASSWORD_PLACEHOLDER'); ?>" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:0 1.5rem 1.5rem; text-align:right;">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary mb-restore-close"><?php echo Text::_('JCANCEL'); ?></button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo Text::_('JCANCEL'); ?></button>
|
||||||
<button type="submit" class="btn btn-danger">
|
<button type="submit" class="btn btn-danger">
|
||||||
<span class="icon-upload" aria-hidden="true"></span>
|
<span class="icon-upload" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_TOOLBAR_RESTORE'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_TOOLBAR_RESTORE'); ?>
|
||||||
@@ -642,44 +637,53 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Restore Progress Modal -->
|
<!-- Restore Progress Modal -->
|
||||||
<div id="mb-restore-progress-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-restore-progress-modal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||||
<div style="max-width:500px; margin:10% auto; background:#fff; border-radius:8px; padding:2rem; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
<div class="modal-dialog">
|
||||||
<h3 id="mb-restore-title" style="margin:0 0 1rem;">Restore in Progress</h3>
|
<div class="modal-content">
|
||||||
<div style="background:#e9ecef; border-radius:4px; overflow:hidden; height:24px; margin-bottom:0.5rem;">
|
<div class="modal-header">
|
||||||
<div id="mb-restore-progress-bar" style="height:100%; background:#dc3545; transition:width 0.3s; width:0%; display:flex; align-items:center; justify-content:center; color:#fff; font-size:0.8rem; font-weight:bold;">0%</div>
|
<h5 class="modal-title" id="mb-restore-title">Restore in Progress</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="progress mb-2" style="height:24px;">
|
||||||
|
<div id="mb-restore-progress-bar" class="progress-bar bg-danger" role="progressbar" style="width:0%;">0%</div>
|
||||||
|
</div>
|
||||||
|
<p id="mb-restore-status" class="text-muted mb-1" style="font-size:0.9rem;">Initializing...</p>
|
||||||
|
<p id="mb-restore-phase" class="text-muted mb-0" style="font-size:0.8rem;">Phase: init</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p id="mb-restore-status" style="color:#666; font-size:0.9rem; margin:0.5rem 0;">Initializing...</p>
|
|
||||||
<p id="mb-restore-phase" style="color:#999; font-size:0.8rem; margin:0;">Phase: init</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Viewer Modal -->
|
<!-- Log Viewer Modal -->
|
||||||
<div id="mb-log-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-log-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:700px; margin:5% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3); display:flex; flex-direction:column; max-height:80vh;">
|
<div class="modal-dialog modal-lg">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;"><?php echo Text::_('COM_MOKOJOOMBACKUP_VIEW_LOG'); ?></h4>
|
<div class="modal-header">
|
||||||
<button type="button" class="btn-close mb-log-close" aria-label="Close"></button>
|
<h5 class="modal-title"><?php echo Text::_('COM_MOKOJOOMBACKUP_VIEW_LOG'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:1rem 1.5rem; overflow-y:auto; flex:1;">
|
<div class="modal-body" style="max-height:60vh; overflow-y:auto;">
|
||||||
<pre id="mb-log-body" style="white-space:pre-wrap; word-break:break-word; font-size:0.85rem; margin:0; background:#f8f9fa; padding:1rem; border-radius:4px;"></pre>
|
<pre id="mb-log-body" style="white-space:pre-wrap; word-break:break-word; font-size:0.85rem; margin:0; background:#f8f9fa; padding:1rem; border-radius:4px;"></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Archive Browser Modal -->
|
<!-- Archive Browser Modal -->
|
||||||
<div id="mb-browse-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-browse-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:800px; margin:5% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3); display:flex; flex-direction:column; max-height:80vh;">
|
<div class="modal-dialog modal-lg">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;">
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
<span class="icon-folder-open" aria-hidden="true"></span>
|
<span class="icon-folder-open" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_BROWSE_ARCHIVE'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_BROWSE_ARCHIVE'); ?>
|
||||||
</h4>
|
</h5>
|
||||||
<button type="button" class="btn-close mb-browse-close" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:0.75rem 1.5rem; border-bottom:1px solid #dee2e6; background:#f8f9fa;">
|
<div class="modal-body p-0">
|
||||||
|
<div class="px-3 py-2 bg-light border-bottom">
|
||||||
<small id="mb-browse-summary" class="text-muted"></small>
|
<small id="mb-browse-summary" class="text-muted"></small>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:0; overflow-y:auto; flex:1;">
|
<div style="max-height:60vh; overflow-y:auto;">
|
||||||
<table class="table table-sm table-striped mb-0">
|
<table class="table table-sm table-striped mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -694,21 +698,24 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Purge Backups Modal -->
|
<!-- Purge Backups Modal -->
|
||||||
<?php $canDelete = $user->authorise('core.delete', 'com_mokosuitebackup'); ?>
|
<?php $canDelete = $user->authorise('core.delete', 'com_mokosuitebackup'); ?>
|
||||||
<?php if ($canDelete) : ?>
|
<?php if ($canDelete) : ?>
|
||||||
<div id="mb-purge-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-purge-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:500px; margin:8% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
<div class="modal-dialog">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;">
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
<span class="icon-trash" aria-hidden="true"></span>
|
<span class="icon-trash" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_TITLE'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_TITLE'); ?>
|
||||||
</h4>
|
</h5>
|
||||||
<button type="button" class="btn-close mb-purge-close" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=backups.purge'); ?>" method="post" id="mb-purge-form">
|
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=backups.purge'); ?>" method="post" id="mb-purge-form">
|
||||||
<div style="padding:1.5rem;">
|
<div class="modal-body">
|
||||||
<p><?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_DESC'); ?></p>
|
<p><?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_DESC'); ?></p>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="mb-purge-date" class="form-label fw-bold"><?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_DATE_LABEL'); ?></label>
|
<label for="mb-purge-date" class="form-label fw-bold"><?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_DATE_LABEL'); ?></label>
|
||||||
@@ -721,8 +728,8 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
<div class="alert alert-info mb-0"><?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_NONE_FOUND'); ?></div>
|
<div class="alert alert-info mb-0"><?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_NONE_FOUND'); ?></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:0 1.5rem 1.5rem; text-align:right;">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary mb-purge-close"><?php echo Text::_('JCANCEL'); ?></button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo Text::_('JCANCEL'); ?></button>
|
||||||
<button type="submit" class="btn btn-danger" id="mb-purge-submit" disabled>
|
<button type="submit" class="btn btn-danger" id="mb-purge-submit" disabled>
|
||||||
<span class="icon-trash" aria-hidden="true"></span>
|
<span class="icon-trash" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_SUBMIT'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_PURGE_SUBMIT'); ?>
|
||||||
@@ -732,20 +739,22 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Backup Comparison Modal -->
|
<!-- Backup Comparison Modal -->
|
||||||
<div id="mb-compare-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-compare-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:800px; margin:5% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3); display:flex; flex-direction:column; max-height:85vh;">
|
<div class="modal-dialog modal-lg">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;">
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
<span class="icon-copy" aria-hidden="true"></span>
|
<span class="icon-copy" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_COMPARE_TITLE'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_COMPARE_TITLE'); ?>
|
||||||
</h4>
|
</h5>
|
||||||
<button type="button" class="btn-close mb-compare-close" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:1rem 1.5rem; overflow-y:auto; flex:1;">
|
<div class="modal-body" style="max-height:65vh; overflow-y:auto;">
|
||||||
<div id="mb-compare-loading" style="text-align:center; padding:2rem;">
|
<div id="mb-compare-loading" class="text-center py-4">
|
||||||
<span class="icon-spinner icon-spin" aria-hidden="true"></span>
|
<span class="icon-spinner icon-spin" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_COMPARE_LOADING'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_COMPARE_LOADING'); ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -764,6 +773,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
@@ -807,7 +817,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
var table = document.getElementById('mb-compare-table');
|
var table = document.getElementById('mb-compare-table');
|
||||||
var body = document.getElementById('mb-compare-body');
|
var body = document.getElementById('mb-compare-body');
|
||||||
|
|
||||||
modal.style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(modal).show();
|
||||||
loading.style.display = 'block';
|
loading.style.display = 'block';
|
||||||
errorEl.style.display = 'none';
|
errorEl.style.display = 'none';
|
||||||
table.style.display = 'none';
|
table.style.display = 'none';
|
||||||
@@ -874,12 +884,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close compare modal
|
// Compare modal close handled by Bootstrap data-bs-dismiss
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
if (e.target.id === 'mb-compare-modal' || e.target.classList.contains('mb-compare-close')) {
|
|
||||||
document.getElementById('mb-compare-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Intercept Compare toolbar button
|
// Intercept Compare toolbar button
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
@@ -922,7 +927,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
document.getElementById('mb-purge-count-wrapper').style.display = 'none';
|
document.getElementById('mb-purge-count-wrapper').style.display = 'none';
|
||||||
document.getElementById('mb-purge-none-wrapper').style.display = 'none';
|
document.getElementById('mb-purge-none-wrapper').style.display = 'none';
|
||||||
document.getElementById('mb-purge-submit').disabled = true;
|
document.getElementById('mb-purge-submit').disabled = true;
|
||||||
document.getElementById('mb-purge-modal').style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('mb-purge-modal')).show();
|
||||||
return false;
|
return false;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
@@ -936,12 +941,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close modal
|
// Purge modal close handled by Bootstrap data-bs-dismiss
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
if (e.target.id === 'mb-purge-modal' || e.target.classList.contains('mb-purge-close')) {
|
|
||||||
document.getElementById('mb-purge-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Confirm on submit
|
// Confirm on submit
|
||||||
var purgeForm = document.getElementById('mb-purge-form');
|
var purgeForm = document.getElementById('mb-purge-form');
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ document.querySelectorAll('.mb-tile').forEach(function(tile) {
|
|||||||
<select id="mb-profile-select" class="form-select mb-2">
|
<select id="mb-profile-select" class="form-select mb-2">
|
||||||
<?php foreach ($this->profiles as $profile) : ?>
|
<?php foreach ($this->profiles as $profile) : ?>
|
||||||
<option value="<?php echo (int) $profile->id; ?>">
|
<option value="<?php echo (int) $profile->id; ?>">
|
||||||
|
#<?php echo (int) $profile->id; ?> —
|
||||||
<?php echo $this->escape($profile->title); ?>
|
<?php echo $this->escape($profile->title); ?>
|
||||||
(<?php echo $this->escape($profile->backup_type); ?>)
|
(<?php echo $this->escape($profile->backup_type); ?>)
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -132,14 +132,15 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Create Snapshot Modal -->
|
<!-- Create Snapshot Modal -->
|
||||||
<div id="mb-snapshot-create-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-snapshot-create-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:500px; margin:8% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
<div class="modal-dialog">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_CREATE'); ?></h4>
|
<div class="modal-header">
|
||||||
<button type="button" class="btn-close mb-modal-close" aria-label="Close"></button>
|
<h5 class="modal-title"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_CREATE'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=snapshots.create'); ?>" method="post" id="mb-snapshot-create-form">
|
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=snapshots.create'); ?>" method="post" id="mb-snapshot-create-form">
|
||||||
<div style="padding:1.5rem;">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="mb-snap-desc" class="form-label fw-bold"><?php echo Text::_('COM_MOKOJOOMBACKUP_HEADING_DESCRIPTION'); ?></label>
|
<label for="mb-snap-desc" class="form-label fw-bold"><?php echo Text::_('COM_MOKOJOOMBACKUP_HEADING_DESCRIPTION'); ?></label>
|
||||||
<input type="text" class="form-control" id="mb-snap-desc" name="description" placeholder="<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_DESC_PLACEHOLDER'); ?>">
|
<input type="text" class="form-control" id="mb-snap-desc" name="description" placeholder="<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_DESC_PLACEHOLDER'); ?>">
|
||||||
@@ -169,8 +170,8 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:0 1.5rem 1.5rem; text-align:right;">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary mb-modal-close"><?php echo Text::_('JCANCEL'); ?></button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo Text::_('JCANCEL'); ?></button>
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<span class="icon-camera" aria-hidden="true"></span>
|
<span class="icon-camera" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_CREATE'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_CREATE'); ?>
|
||||||
@@ -180,17 +181,19 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Restore Snapshot Modal -->
|
<!-- Restore Snapshot Modal -->
|
||||||
<div id="mb-snapshot-restore-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-snapshot-restore-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:500px; margin:8% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
<div class="modal-dialog">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE'); ?></h4>
|
<div class="modal-header">
|
||||||
<button type="button" class="btn-close mb-modal-close" aria-label="Close"></button>
|
<h5 class="modal-title"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=snapshots.restore'); ?>" method="post" id="mb-snapshot-restore-form">
|
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=snapshots.restore'); ?>" method="post" id="mb-snapshot-restore-form">
|
||||||
<input type="hidden" name="id" id="mb-restore-id" value="">
|
<input type="hidden" name="id" id="mb-restore-id" value="">
|
||||||
<div style="padding:1.5rem;">
|
<div class="modal-body">
|
||||||
<p id="mb-restore-desc" class="fw-bold"></p>
|
<p id="mb-restore-desc" class="fw-bold"></p>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -213,7 +216,6 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
|
|
||||||
<div class="mb-3" id="mb-restore-types-container">
|
<div class="mb-3" id="mb-restore-types-container">
|
||||||
<label class="form-label fw-bold"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE_TYPES'); ?></label>
|
<label class="form-label fw-bold"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE_TYPES'); ?></label>
|
||||||
<!-- Populated by JS from data-types -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-warning mb-0" id="mb-replace-warning">
|
<div class="alert alert-warning mb-0" id="mb-replace-warning">
|
||||||
@@ -221,8 +223,8 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_REPLACE_WARNING'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_REPLACE_WARNING'); ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:0 1.5rem 1.5rem; text-align:right;">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary mb-modal-close"><?php echo Text::_('JCANCEL'); ?></button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo Text::_('JCANCEL'); ?></button>
|
||||||
<button type="submit" class="btn btn-danger">
|
<button type="submit" class="btn btn-danger">
|
||||||
<span class="icon-upload" aria-hidden="true"></span>
|
<span class="icon-upload" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE'); ?>
|
||||||
@@ -232,17 +234,19 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Browse Snapshot Detail Modal -->
|
<!-- Browse Snapshot Detail Modal -->
|
||||||
<div id="mb-snapshot-browse-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
<div class="modal fade" id="mb-snapshot-browse-modal" tabindex="-1" aria-hidden="true">
|
||||||
<div style="max-width:800px; margin:5% auto; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3); max-height:80vh; display:flex; flex-direction:column;">
|
<div class="modal-dialog modal-xl">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; border-bottom:1px solid #dee2e6;">
|
<div class="modal-content">
|
||||||
<h4 style="margin:0;" id="mb-browse-title"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_BROWSE'); ?></h4>
|
<div class="modal-header">
|
||||||
<button type="button" class="btn-close mb-modal-close" aria-label="Close"></button>
|
<h5 class="modal-title" id="mb-browse-title"><?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_BROWSE'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=snapshots.restoreSelected'); ?>" method="post" id="mb-snapshot-browse-form">
|
<form action="<?php echo Route::_('index.php?option=com_mokosuitebackup&task=snapshots.restoreSelected'); ?>" method="post" id="mb-snapshot-browse-form">
|
||||||
<input type="hidden" name="id" id="mb-browse-id" value="">
|
<input type="hidden" name="id" id="mb-browse-id" value="">
|
||||||
<div style="padding:1rem 1.5rem; overflow-y:auto; flex:1;">
|
<div class="modal-body" style="max-height:60vh; overflow-y:auto;">
|
||||||
<div id="mb-browse-loading" class="text-center py-4">
|
<div id="mb-browse-loading" class="text-center py-4">
|
||||||
<span class="spinner-border spinner-border-sm" role="status"></span>
|
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_LOADING'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_LOADING'); ?>
|
||||||
@@ -331,8 +335,8 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:0.75rem 1.5rem; border-top:1px solid #dee2e6; text-align:right;">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary mb-modal-close"><?php echo Text::_('JCANCEL'); ?></button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo Text::_('JCANCEL'); ?></button>
|
||||||
<button type="submit" class="btn btn-success" id="mb-browse-restore-btn" disabled>
|
<button type="submit" class="btn btn-success" id="mb-browse-restore-btn" disabled>
|
||||||
<span class="icon-upload" aria-hidden="true"></span>
|
<span class="icon-upload" aria-hidden="true"></span>
|
||||||
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE_SELECTED'); ?>
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE_SELECTED'); ?>
|
||||||
@@ -342,6 +346,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
@@ -352,7 +357,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
createBtn.addEventListener('click', function(e) {
|
createBtn.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
document.getElementById('mb-snapshot-create-modal').style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('mb-snapshot-create-modal')).show();
|
||||||
return false;
|
return false;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
@@ -413,7 +418,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
// Show/hide replace warning based on mode
|
// Show/hide replace warning based on mode
|
||||||
toggleReplaceWarning();
|
toggleReplaceWarning();
|
||||||
|
|
||||||
document.getElementById('mb-snapshot-restore-modal').style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('mb-snapshot-restore-modal')).show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle warning when mode changes
|
// Toggle warning when mode changes
|
||||||
@@ -454,7 +459,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
tab.show();
|
tab.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('mb-snapshot-browse-modal').style.display = 'block';
|
bootstrap.Modal.getOrCreateInstance(document.getElementById('mb-snapshot-browse-modal')).show();
|
||||||
|
|
||||||
// Fetch snapshot content via AJAX
|
// Fetch snapshot content via AJAX
|
||||||
var token = <?php echo json_encode(Session::getFormToken()); ?>;
|
var token = <?php echo json_encode(Session::getFormToken()); ?>;
|
||||||
@@ -617,16 +622,6 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
|||||||
: <?php echo json_encode(Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE_SELECTED')); ?>;
|
: <?php echo json_encode(Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_RESTORE_SELECTED')); ?>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close modals
|
// Modal close handled by Bootstrap data-bs-dismiss
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
if (e.target.classList.contains('mb-modal-close') ||
|
|
||||||
e.target.id === 'mb-snapshot-create-modal' ||
|
|
||||||
e.target.id === 'mb-snapshot-restore-modal' ||
|
|
||||||
e.target.id === 'mb-snapshot-browse-modal') {
|
|
||||||
document.getElementById('mb-snapshot-create-modal').style.display = 'none';
|
|
||||||
document.getElementById('mb-snapshot-restore-modal').style.display = 'none';
|
|
||||||
document.getElementById('mb-snapshot-browse-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="module" client="administrator" method="upgrade">
|
<extension type="module" client="administrator" method="upgrade">
|
||||||
<name>mod_mokosuitebackup_cpanel</name>
|
<name>mod_mokosuitebackup_cpanel</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-23</creationDate>
|
<creationDate>2026-06-23</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ $moduleId = 'mod-msb-cpanel-' . $displayData['module']->id;
|
|||||||
class="btn btn-sm btn-outline-primary msb-cpanel-backup-btn"
|
class="btn btn-sm btn-outline-primary msb-cpanel-backup-btn"
|
||||||
data-profile-id="<?php echo (int) $profile->id; ?>"
|
data-profile-id="<?php echo (int) $profile->id; ?>"
|
||||||
data-module-id="<?php echo $moduleId; ?>">
|
data-module-id="<?php echo $moduleId; ?>">
|
||||||
<?php echo htmlspecialchars($profile->title); ?>
|
#<?php echo (int) $profile->id; ?> <?php echo htmlspecialchars($profile->title); ?>
|
||||||
<span class="badge bg-secondary ms-1"><?php echo htmlspecialchars($profile->backup_type); ?></span>
|
<span class="badge bg-secondary ms-1"><?php echo htmlspecialchars($profile->backup_type); ?></span>
|
||||||
</button>
|
</button>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="actionlog" method="upgrade">
|
<extension type="plugin" group="actionlog" method="upgrade">
|
||||||
<name>Action Log - MokoSuiteBackup</name>
|
<name>Action Log - MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="console" method="upgrade">
|
<extension type="plugin" group="console" method="upgrade">
|
||||||
<name>Console - MokoSuiteBackup</name>
|
<name>Console - MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="content" method="upgrade">
|
<extension type="plugin" group="content" method="upgrade">
|
||||||
<name>Content - MokoSuiteBackup</name>
|
<name>Content - MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="quickicon" method="upgrade">
|
<extension type="plugin" group="quickicon" method="upgrade">
|
||||||
<name>Quick Icon - MokoSuiteBackup</name>
|
<name>Quick Icon - MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="system" method="upgrade">
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
<name>System - MokoSuiteBackup</name>
|
<name>System - MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="task" method="upgrade">
|
<extension type="plugin" group="task" method="upgrade">
|
||||||
<name>Task - MokoSuiteBackup</name>
|
<name>Task - MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="webservices" method="upgrade">
|
<extension type="plugin" group="webservices" method="upgrade">
|
||||||
<name>Web Services - MokoSuiteBackup</name>
|
<name>Web Services - MokoSuiteBackup</name>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<extension type="package" method="upgrade">
|
<extension type="package" method="upgrade">
|
||||||
<name>Package - MokoSuiteBackup</name>
|
<name>Package - MokoSuiteBackup</name>
|
||||||
<packagename>mokosuitebackup</packagename>
|
<packagename>mokosuitebackup</packagename>
|
||||||
<version>01.43.00</version>
|
<version>01.43.07</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
Reference in New Issue
Block a user