diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9e39697a38799..a4b5255e3257a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -166,6 +166,7 @@ The development container provides several convenience features for development: * Pay attention to the IONOS-specific scripts and configurations when working on related features. * Use the `IONOS/configure.sh` script for most configuration tasks rather than manual OCC commands. * Before committing any changes, make sure to run the relevant tests and linters. +* **Recurring Tasks Documentation:** For common development tasks (e.g., delegating settings to admin, adding apps, etc.), check `..//docs/recurring-tasks/` for step-by-step guides. ## Code Organization & Structure diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index ae9148c3ffc06..c71999218bd82 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -32,6 +32,7 @@ on: - ionos-dev - ionos-stable - 'rc/**' + - '*/dev/*' workflow_dispatch: # Manual trigger to bypass all caches inputs: force_rebuild: @@ -153,11 +154,11 @@ jobs: echo " ✅ Event type is 'push'" fi - # Check if branch matches expected patterns: ionos-dev, ionos-stable, or rc/* - VALID_BRANCH_PATTERN='^(ionos-dev|ionos-stable)$|^rc/.*$' + # Check if branch matches expected patterns: ionos-dev, ionos-stable, rc/* or */dev/* + VALID_BRANCH_PATTERN='^(ionos-dev|ionos-stable)$|^rc/.*$|^[^/]+/dev/.*$' if [[ ! "${{ github.ref_name }}" =~ $VALID_BRANCH_PATTERN ]]; then - echo "- ❌ Branch must be 'ionos-dev', 'ionos-stable', or 'rc/*' (current: \`${{ github.ref_name }}\`)" >> $GITHUB_STEP_SUMMARY - echo " ❌ Branch is '${{ github.ref_name }}' (must be 'ionos-dev', 'ionos-stable', or 'rc/*')" + echo "- ❌ Branch must be 'ionos-dev', 'ionos-stable', 'rc/*' or '*/dev/*' (current: \`${{ github.ref_name }}\`)" >> $GITHUB_STEP_SUMMARY + echo " ❌ Branch is '${{ github.ref_name }}' (must be 'ionos-dev', 'ionos-stable', 'rc/*' or '*/dev/*')" WILL_TRIGGER=false else echo "- ✅ Branch is '\`${{ github.ref_name }}\`'" >> $GITHUB_STEP_SUMMARY @@ -478,7 +479,6 @@ jobs: fetch-depth: 1 - name: Setup JFrog CLI - if: needs.prepare-matrix.outputs.has_apps_to_restore == 'true' uses: jfrog/setup-jfrog-cli@7c95feb32008765e1b4e626b078dfd897c4340ad # v4.4.1 env: JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }} @@ -490,29 +490,111 @@ jobs: # Ping the server jf rt ping - - name: Restore cached apps - if: needs.prepare-matrix.outputs.has_apps_to_restore == 'true' + - name: Restore all apps from cache and JFrog run: | - set -e + set -euo pipefail + + # Restore apps that are in cache (from apps_to_restore) + if [ "${{ needs.prepare-matrix.outputs.has_apps_to_restore }}" == "true" ]; then + echo "📦 Restoring cached apps..." + APPS_TO_RESTORE='${{ needs.prepare-matrix.outputs.apps_to_restore }}' + + # Process each app in the restore list + echo "$APPS_TO_RESTORE" | jq -c '.[]' | while read -r app_json; do + APP_NAME=$(echo "$app_json" | jq -r '.name') + APP_SHA=$(echo "$app_json" | jq -r '.sha') + APP_PATH=$(echo "$app_json" | jq -r '.path') + SOURCE=$(echo "$app_json" | jq -r '.source') + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "Restoring: $APP_NAME (source: $SOURCE)" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + if [ "$SOURCE" == "jfrog" ]; then + # Restore from JFrog + JFROG_PATH=$(echo "$app_json" | jq -r '.jfrog_path') + ARCHIVE_NAME="${APP_NAME}-${APP_SHA}.tar.gz" + + echo "📥 Downloading from JFrog: $JFROG_PATH" + + if jf rt download "$JFROG_PATH" "$ARCHIVE_NAME" --flat=true; then + echo "✅ Downloaded successfully" + echo "Extracting to $APP_PATH..." + mkdir -p "$(dirname "$APP_PATH")" + tar -xzf "$ARCHIVE_NAME" -C "$(dirname "$APP_PATH")" + + if [ -d "$APP_PATH" ] && [ -f "$APP_PATH/appinfo/info.xml" ]; then + echo "✅ Restored $APP_NAME from JFrog" + else + echo "❌ Failed to extract or validate $APP_NAME" + exit 1 + fi + + rm -f "$ARCHIVE_NAME" + else + echo "❌ Failed to download from JFrog" + exit 1 + fi - echo "📦 Restoring cached apps..." - APPS_TO_RESTORE='${{ needs.prepare-matrix.outputs.apps_to_restore }}' + elif [ "$SOURCE" == "github-cache" ]; then + # Restore from GitHub cache + CACHE_KEY=$(echo "$app_json" | jq -r '.cache_key') - # Process each app in the restore list - echo "$APPS_TO_RESTORE" | jq -c '.[]' | while read -r app_json; do - APP_NAME=$(echo "$app_json" | jq -r '.name') - APP_SHA=$(echo "$app_json" | jq -r '.sha') - APP_PATH=$(echo "$app_json" | jq -r '.path') - SOURCE=$(echo "$app_json" | jq -r '.source') + echo "💾 Restoring from GitHub cache: $CACHE_KEY" + + # Use actions/cache/restore in a way that works in a script context + # We need to use gh CLI to restore the cache + if gh cache restore "$CACHE_KEY" --key "$CACHE_KEY"; then + echo "✅ Restored $APP_NAME from GitHub cache" + + # Validate restoration + if [ ! -d "$APP_PATH" ] || [ ! -f "$APP_PATH/appinfo/info.xml" ]; then + echo "❌ Validation failed for $APP_NAME" + exit 1 + fi + else + echo "❌ Failed to restore from GitHub cache" + exit 1 + fi + else + echo "❌ Unknown source: $SOURCE" + exit 1 + fi + done echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "Restoring: $APP_NAME (source: $SOURCE)" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ All cached apps restored successfully" + fi + + # Also restore newly built apps from JFrog (from apps_to_build) + if [ "${{ needs.prepare-matrix.outputs.has_apps_to_build }}" == "true" ]; then + echo "" + echo "📦 Restoring newly built apps from JFrog..." + APPS_TO_BUILD='${{ needs.prepare-matrix.outputs.apps_to_build }}' + FULL_MATRIX='${{ needs.prepare-matrix.outputs.external_apps_matrix }}' + EFFECTIVE_CACHE_VERSION="${{ needs.prepare-matrix.outputs.effective_cache_version }}" + + # Process each app in the build list + echo "$APPS_TO_BUILD" | jq -c '.[]' | while read -r app_json; do + APP_NAME=$(echo "$app_json" | jq -r '.name') + APP_SHA=$(echo "$app_json" | jq -r '.sha') + + # Get app path from full matrix + APP_PATH=$(echo "$FULL_MATRIX" | jq -r --arg name "$APP_NAME" '.[] | select(.name == $name) | .path') - if [ "$SOURCE" == "jfrog" ]; then - # Restore from JFrog - JFROG_PATH=$(echo "$app_json" | jq -r '.jfrog_path') + if [ -z "$APP_PATH" ] || [ "$APP_PATH" == "null" ]; then + echo "❌ Could not find path for $APP_NAME in matrix" + exit 1 + fi + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "Restoring newly built: $APP_NAME" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + # Construct JFrog path matching what build-external-apps uploaded + JFROG_PATH="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/apps/${EFFECTIVE_CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${APP_SHA}.tar.gz" ARCHIVE_NAME="${APP_NAME}-${APP_SHA}.tar.gz" echo "📥 Downloading from JFrog: $JFROG_PATH" @@ -535,88 +617,19 @@ jobs: echo "❌ Failed to download from JFrog" exit 1 fi + done - elif [ "$SOURCE" == "github-cache" ]; then - # Restore from GitHub cache - CACHE_KEY=$(echo "$app_json" | jq -r '.cache_key') - - echo "💾 Restoring from GitHub cache: $CACHE_KEY" - - # Use actions/cache/restore in a way that works in a script context - # We need to use gh CLI to restore the cache - if gh cache restore "$CACHE_KEY" --key "$CACHE_KEY"; then - echo "✅ Restored $APP_NAME from GitHub cache" - - # Validate restoration - if [ ! -d "$APP_PATH" ] || [ ! -f "$APP_PATH/appinfo/info.xml" ]; then - echo "❌ Validation failed for $APP_NAME" - exit 1 - fi - else - echo "❌ Failed to restore from GitHub cache" - exit 1 - fi - else - echo "❌ Unknown source: $SOURCE" - exit 1 - fi - done + echo "" + echo "✅ All newly built apps restored from JFrog successfully" + fi echo "" - echo "✅ All cached apps restored successfully" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ All apps restored successfully" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" env: GH_TOKEN: ${{ github.token }} - - name: Download build external apps - uses: actions/download-artifact@v5 - with: - pattern: external-app-build-* - path: apps-external/ - - - name: Reorganize downloaded apps-external artifacts - run: | - cd apps-external/ - - echo "Initial structure:" - ls -la - - # Move contents from external-app-build-* directories to their target directories - for artifact_dir in external-app-build-*; do - if [ -d "$artifact_dir" ]; then - # Extract app name from artifact directory name - app_name=${artifact_dir#external-app-build-} - - echo "Processing artifact: $artifact_dir -> $app_name" - - # If target directory exists, merge the contents from the artifact directory containing build artifacts - if [ -d "$app_name" ]; then - echo "Target directory $app_name exists, merging contents from $artifact_dir" - # Copy contents from artifact directory to target directory - cp -r "$artifact_dir"/* "$app_name"/ - # Remove the now-empty artifact directory - rm -rf "$artifact_dir" - else - # Move the artifact directory to the proper app name - echo "Moving $artifact_dir to $app_name" - mv "$artifact_dir" "$app_name" - fi - fi - done - - echo "Reorganization complete. Final structure:" - ls -la - - - name: Verify downloaded artifacts structure - run: | - echo "External apps structure:" - ls -la apps-external/ - for app_dir in apps-external/*/; do - if [ -d "$app_dir" ]; then - echo "Contents of $app_dir:" - ls -la "$app_dir" - fi - done - - name: Set up node with version from package.json's engines uses: actions/setup-node@v6 with: @@ -698,10 +711,10 @@ jobs: upload-to-artifactory: runs-on: self-hosted - # Upload the artifact to the Artifactory repository on PR *OR* on "ionos-dev|ionos-stable|rc/*" branch push defined in the on:push:branches + # Upload the artifact to the Artifactory repository on PR *OR* on "ionos-dev|ionos-stable|rc/*|*/dev/*" branch push defined in the on:push:branches *OR* on manual workflow_dispatch if: | always() && - (github.event_name == 'pull_request' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' || startsWith(github.ref_name, 'rc/')) && + (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' || startsWith(github.ref_name, 'rc/') || contains(github.ref_name, '/dev/')) && needs.prepare-matrix.result == 'success' && (needs.build-external-apps.result == 'success' || needs.build-external-apps.result == 'skipped') && needs.build-artifact.result == 'success' @@ -781,12 +794,13 @@ jobs: id: artifactory_upload run: | # Artifactory Build Storage Structure: - # | Branch/Event | Stage Prefix | Artifact Path | - # |------------------|----------------|--------------------------------------------------------------------------| - # | Pull Request | dev | dev/pr/nextcloud-workspace-pr-.zip | - # | ionos-dev | dev | dev/ncw-//nextcloud-workspace-.zip | - # | ionos-stable | stable | stable/ncw-//nextcloud-workspace-.zip | + # | Branch/Event | Stage Prefix | Artifact Path | + # |------------------|----------------|--------------------------------------------------------------------------------------------| + # | Pull Request | dev | dev/pr/nextcloud-workspace-pr-.zip | + # | ionos-dev | dev | dev/ncw-//nextcloud-workspace-.zip | + # | ionos-stable | stable | stable/ncw-//nextcloud-workspace-.zip | # | rc/* | rc | rc//ncw-//nextcloud-workspace-.zip | + # | */dev/* | dev-* | dev-/ncw-//nextcloud-workspace-.zip | ARTIFACTORY_STAGE_PREFIX="dev" @@ -796,6 +810,11 @@ jobs: # set ARTIFACTORY_STAGE_PREFIX=rc on rc/* branches elif [[ "${{ github.ref_name }}" =~ ^rc/.*$ ]]; then ARTIFACTORY_STAGE_PREFIX="${{ github.ref_name }}" + # Extract prefix from */dev/* branches (e.g., 'ah/dev/feature' -> 'dev-ah') + elif [[ "${{ github.ref_name }}" =~ ^.*/dev/.*$ ]]; then + # Extract the part before '/dev/' + BRANCH_PREFIX=$(echo "${{ github.ref_name }}" | sed 's|/.*||' | sed 's/[^A-Za-z0-9._-]/-/g') + ARTIFACTORY_STAGE_PREFIX="dev-${BRANCH_PREFIX}" fi export PATH_TO_DIRECTORY="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/${ARTIFACTORY_STAGE_PREFIX}" @@ -946,13 +965,13 @@ jobs: name: Trigger remote workflow needs: [build-artifact, upload-to-artifactory] - # Trigger remote build on "ionos-dev|ionos-stable|rc/*" branch *push* defined in the on:push:branches + # Trigger remote build on "ionos-dev|ionos-stable|rc/*|*/dev/*" branch *push* defined in the on:push:branches # Can be disabled via repository variable 'DISABLE_REMOTE_TRIGGER' (set to 'true' to disable) # Configure at: https://github.com/IONOS-Productivity/ncw-server/settings/variables/actions if: | always() && github.event_name == 'push' && - (github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' || startsWith(github.ref_name, 'rc/')) && + (github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' || startsWith(github.ref_name, 'rc/') || contains(github.ref_name, '/dev/')) && needs.build-artifact.result == 'success' && needs.upload-to-artifactory.result == 'success' && vars.DISABLE_REMOTE_TRIGGER != 'true' @@ -1028,6 +1047,7 @@ jobs: # | ionos-dev | main | dev | # | ionos-stable | main | stable | # | rc/* | main | rc | + # | */dev/* | main | dev-* | BUILD_TYPE="dev" @@ -1037,6 +1057,11 @@ jobs: # Override build type for rc/* branches elif [[ "${{ github.ref_name }}" =~ ^rc/ ]]; then BUILD_TYPE="rc" + # Extract prefix from */dev/* branches (e.g., 'ah/dev/feature' -> 'dev-ah') + elif [[ "${{ github.ref_name }}" =~ ^.*/dev/.*$ ]]; then + # Extract the part before '/dev/' + BRANCH_PREFIX=$(echo "${{ github.ref_name }}" | sed 's|/.*||' | sed 's/[^A-Za-z0-9._-]/-/g') + BUILD_TYPE="dev-${BRANCH_PREFIX}" fi # Construct source build URL for traceability diff --git a/.gitmodules b/.gitmodules index a7c0d60e7e858..a612546b88da1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,7 +24,7 @@ url = git@github.com:nextcloud/notify_push.git [submodule "apps-external/activity"] path = apps-external/activity - url = git@github.com:nextcloud/activity.git + url = git@github.com:IONOS-Productivity/nc-activity.git [submodule "apps-external/tasks"] path = apps-external/tasks url = git@github.com:nextcloud/tasks.git @@ -81,7 +81,7 @@ url = git@github.com:IONOS-Productivity/nc-notifications.git [submodule "apps-external/user_oidc"] path = apps-external/user_oidc - url = git@github.com:nextcloud/user_oidc.git + url = git@github.com:IONOS-Productivity/nc-user_oidc.git [submodule "apps-external/end_to_end_encryption"] path = apps-external/end_to_end_encryption url = git@github.com:nextcloud/end_to_end_encryption.git @@ -91,3 +91,9 @@ [submodule "apps-external/ncw_tools"] path = apps-external/ncw_tools url = git@github.com:IONOS-Productivity/ncw-tools.git +[submodule "apps-external/files_pdfviewer"] + path = apps-external/files_pdfviewer + url = git@github.com:nextcloud/files_pdfviewer.git +[submodule "apps-external/bruteforcesettings"] + path = apps-external/bruteforcesettings + url = git@github.com:IONOS-Productivity/nc-bruteforcesettings.git diff --git a/IONOS b/IONOS index 66c2a3ad8087a..30be7051586fa 160000 --- a/IONOS +++ b/IONOS @@ -1 +1 @@ -Subproject commit 66c2a3ad8087a35b4f276990bd53aa66c948c969 +Subproject commit 30be7051586faae63a143d061925045dd800dba5 diff --git a/apps-external/activity b/apps-external/activity index d5a969a1c52a7..ca3cd2e5e5883 160000 --- a/apps-external/activity +++ b/apps-external/activity @@ -1 +1 @@ -Subproject commit d5a969a1c52a7fbc42431df0949f383933419576 +Subproject commit ca3cd2e5e58839d62e5ec3e6afbac0b58d52f8a0 diff --git a/apps-external/bruteforcesettings b/apps-external/bruteforcesettings new file mode 160000 index 0000000000000..423a1065c682b --- /dev/null +++ b/apps-external/bruteforcesettings @@ -0,0 +1 @@ +Subproject commit 423a1065c682be3cb065bdc91c78353fee36c798 diff --git a/apps-external/files_pdfviewer b/apps-external/files_pdfviewer new file mode 160000 index 0000000000000..21125af4ab4a8 --- /dev/null +++ b/apps-external/files_pdfviewer @@ -0,0 +1 @@ +Subproject commit 21125af4ab4a8c2f9784fc4cd66cdae85a41ed4e diff --git a/apps-external/ncw_tools b/apps-external/ncw_tools index 001285c22ae00..e1a0bec138cd7 160000 --- a/apps-external/ncw_tools +++ b/apps-external/ncw_tools @@ -1 +1 @@ -Subproject commit 001285c22ae0028bdcf5ac66654d8121ea472375 +Subproject commit e1a0bec138cd78601753a692d63ef22ce971a6ea diff --git a/apps-external/user_oidc b/apps-external/user_oidc index 192826636cc8b..085f98727f738 160000 --- a/apps-external/user_oidc +++ b/apps-external/user_oidc @@ -1 +1 @@ -Subproject commit 192826636cc8b25acfb43a56617db0758869f808 +Subproject commit 085f98727f73886e3144cf0b65e6580aebd17d87 diff --git a/apps/dashboard/src/DashboardApp.vue b/apps/dashboard/src/DashboardApp.vue index f9a21d44db992..2b2c391da1ff9 100644 --- a/apps/dashboard/src/DashboardApp.vue +++ b/apps/dashboard/src/DashboardApp.vue @@ -630,7 +630,7 @@ export default { &:hover, &:focus, &:active { - background-color: var(--color-background-hover)!important; + background-color: var(--color-background-hover); } &:focus-visible { box-shadow: 0 0 0 4px var(--color-main-background) !important; diff --git a/apps/settings/lib/Settings/Personal/ServerDevNotice.php b/apps/settings/lib/Settings/Personal/ServerDevNotice.php index 71c83740b9291..916d69e97d1da 100644 --- a/apps/settings/lib/Settings/Personal/ServerDevNotice.php +++ b/apps/settings/lib/Settings/Personal/ServerDevNotice.php @@ -15,6 +15,7 @@ use OCP\Settings\ISettings; use OCP\Support\Subscription\IRegistry; use OCP\Util; +use OCP\IConfig; class ServerDevNotice implements ISettings { @@ -25,6 +26,7 @@ public function __construct( private IUserSession $userSession, private IInitialState $initialState, private IURLGenerator $urlGenerator, + private IConfig $config, ) { } @@ -62,6 +64,10 @@ public function getSection(): ?string { return null; } + if ($this->config->getSystemValueBool('settings.hide-dev-notice')) { + return null; + } + return 'personal-info'; } diff --git a/apps/theming/css/ionos/buttons.css b/apps/theming/css/ionos/buttons.css index ed9ae2554362e..e2d8f37ddbeb9 100644 --- a/apps/theming/css/ionos/buttons.css +++ b/apps/theming/css/ionos/buttons.css @@ -35,11 +35,6 @@ } &:hover:not(:disabled):not(.button-vue--disabled) { - background-color: var(--ion-button-secondary-background-hover); - border-color: var(--ion-button-secondary-background-hover); - } - - #app-dashboard &:hover:not(:disabled):not(.button-vue--disabled) { background-color: var(--ion-button-secondary-background-hover) !important; border-color: var(--ion-button-secondary-background-hover); color: var(--ion-button-secondary-text-hover); @@ -69,18 +64,14 @@ } } - &.button-vue--vue-primary:not(.unified-search-modal__header *), &.button-vue--vue-error:not(.unified-search-modal__header *), - /* ensure primary type styling of "new" button */ - &.action-item__menutoggle:not(.unified-search-modal__header *), &.files-list__header-upload-button--disabled { - background-color: var(--ion-button-primary-background-default); - } - #app-dashboard &.action-item__menutoggle:not(.unified-search-modal__header *), #app-dashboard &.files-list__header-upload-button--disabled { - background-color: var(--ion-button-primary-background-default) !important; + background-color: light-dark(var(--ion-color-blue-b7), var(--ion-button-secondary-background-default)); + border: var(--ion-button-secondary-border-default); } &.button-vue--vue-primary:not(.unified-search-modal__header *), &.button-vue--vue-error:not(.unified-search-modal__header *), &.action-item__menutoggle:not(.unified-search-modal__header *), &.files-list__header-upload-button--disabled { + background-color: var(--ion-button-primary-background-default); border: none; .button-vue__text, .button-vue__icon svg {