diff --git a/.editorconfig b/.editorconfig index a612a7a..202ea4a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,8 @@ end_of_line = crlf trim_trailing_whitespace = true [*.cs] -dotnet_sort_system_directives_first = true \ No newline at end of file +dotnet_sort_system_directives_first = true + +# Skyline ignores +dotnet_diagnostic.SLC_SC0001.severity = none +dotnet_diagnostic.SLC_SC0002.severity = none \ No newline at end of file diff --git a/.github/workflows/DataMiner App Packages Master Workflow Custom.yml b/.github/workflows/DataMiner App Packages Master Workflow Custom.yml deleted file mode 100644 index 2fd0491..0000000 --- a/.github/workflows/DataMiner App Packages Master Workflow Custom.yml +++ /dev/null @@ -1,424 +0,0 @@ -name: DataMiner App Packages (Custom) - -# Controls when the workflow will run -on: - # Allows you to run this workflow from another workflow - workflow_call: - inputs: - referenceName: - required: true - type: string - runNumber: - required: true - type: string - referenceType: - required: true - type: string - repository: - required: true - type: string - owner: - required: true - type: string - sonarCloudProjectName: - required: true - type: string - configuration: - required: true - type: string - solutionFilterName: - required: false - type: string - debug: - required: false - type: boolean - secrets: - sonarCloudToken: - required: true - dataminerToken: - required: false - azureToken: - required: false - overrideCatalogDownloadToken: - required: false - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - skyline_cicd: - name: Skyline CICD - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET Core - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - - - name: Cache and Install Mono - uses: awalsh128/cache-apt-pkgs-action@v1.5.0 - with: - packages: mono-complete - - - name: Validate SonarCloud Project Name - id: validate-sonar-name - run: | - if [[ -z "${{ inputs.sonarCloudProjectName }}" ]]; then - echo "Error: sonarCloudProjectName is not set." - echo "Please create a SonarCloud project by visiting: https://sonarcloud.io/projects/create and copy the id of the project as mentioned in the sonarcloud project url." - repo_url="https://github.com/${{ github.repository }}/settings/variables/actions" - echo "Then set a SONAR_NAME variable in your repository settings: $repo_url" - echo "Alternatively, if you do not wish to use the Skyline Quality Gate but intend to publish your results to the catalog, you may create your workflow to include the equivalent of a dotnet publish step as shown below (remove the \\):" - echo " - name: Publish" - echo " env:" - echo " api-key: $\{{ secrets.DATAMINER_TOKEN }}" - echo " run: dotnet publish -p:Version=\"0.0.$\{{ github.run_number }}\" -p:VersionComment=\"Iterative Development\" -p:CatalogPublishKeyName=api-key" - exit 1 - fi - - - name: Validate SonarCloud Secret Token - id: validate-sonar-token - run: | - if [[ -z "${{ secrets.sonarCloudToken }}" ]]; then - echo "Error: sonarCloudToken is not set." - echo "Please create a SonarCloud token by visiting: https://sonarcloud.io/account/security and copy the value of the created token." - repo_url="https://github.com/${{ github.repository }}/settings/secrets/actions" - echo "Then set a SONAR_TOKEN secret in your repository settings: $repo_url" - echo "Alternatively, if you do not wish to use the Skyline Quality Gate but intend to publish your results to the catalog, you may create your workflow to include the equivalent of a dotnet publish step as shown below (remove the \\):" - echo " - name: Publish" - echo " env:" - echo " api-key: $\{{ secrets.DATAMINER_TOKEN }}" - echo " run: dotnet publish -p:Version=\"0.0.$\{{ github.run_number }}\" -p:VersionComment=\"Iterative Development\" -p:CatalogPublishKeyName=api-key" - exit 1 - fi - - - name: Validate DataMiner Secret Token - id: validate-dataminer-token - if: inputs.referenceType == 'tag' - run: | - if [[ -z "${{ secrets.dataminerToken }}" ]]; then - echo "Error: dataminerToken is not set. Release not possible!" - echo "Please create or re-use an admin.dataminer.services token by visiting: https://admin.dataminer.services/." - echo "Navigate to the right organization, then go to Keys and create or find a key with the permissions Register catalog items, Download catalog versions, and Read catalog items." - echo "Copy the value of the token." - repo_url="https://github.com/${{ github.repository }}/settings/secrets/actions" - echo "Then set a DATAMINER_TOKEN secret in your repository settings: $repo_url" - exit 1 - fi - - - name: Find .sln file - id: findSlnFile - run: | - if [[ -z "${{ inputs.solutionFilterName }}" ]]; then - echo solutionFilePath=$(find . -type f -name '*.sln') >> $GITHUB_OUTPUT - else - echo solutionFilePath=$(find . -type f -name '${{ inputs.solutionFilterName }}') >> $GITHUB_OUTPUT - fi - shell: bash - - - name: Enable Skyline NuGet Registries - if: inputs.owner == 'SkylineCommunications' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - AZURE_TOKEN: ${{ secrets.azureToken }} - run: | - $sources = @( - @{ Name = "PrivateGitHubNugets"; URL = "https://nuget.pkg.github.com/SkylineCommunications/index.json"; Username = "USERNAME"; Password = "${{ secrets.GITHUB_TOKEN }}" }, - @{ Name = "CloudNuGets"; URL = "https://pkgs.dev.azure.com/skyline-cloud/Cloud_NuGets/_packaging/CloudNuGet/nuget/v3/index.json"; Username = "az"; Password = "${{ secrets.azureToken }}" }, - @{ Name = "PrivateAzureNuGets"; URL = "https://pkgs.dev.azure.com/skyline-cloud/_packaging/skyline-private-nugets/nuget/v3/index.json"; Username = "az"; Password = "${{ secrets.azureToken }}" } - ) - - foreach ($source in $sources) { - if ($source.Password -ne "") { - Write-Host "Checking source $($source.Name)..." - - if (dotnet nuget list source | Select-String -Pattern $source.Name) { - Write-Host "Updating existing source $($source.Name)." - dotnet nuget update source $source.Name --source $source.URL --username $source.Username --password $source.Password --store-password-in-clear-text - } else { - Write-Host "Adding new source $($source.Name)." - dotnet nuget add source $source.URL --name $source.Name --username $source.Username --password $source.Password --store-password-in-clear-text - } - } else { - Write-Host "Skipping $($source.Name) because the password is not set." - } - } - shell: pwsh - - - name: Install Tools - run: | - dotnet tool install dotnet-sonarscanner --global - dotnet tool install Skyline.DataMiner.CICD.Tools.Sbom --global --version 1.0.* - dotnet tool install -g Skyline.DataMiner.CICD.Tools.CatalogUpload --version 3.0.* - - - name: Prepare SonarCloud Variables - id: prepSonarCloudVar - run: | - import os - env_file = os.getenv('GITHUB_ENV') - with open(env_file, "a") as myfile: - myfile.write("lowerCaseOwner=" + str.lower("${{ inputs.owner }}")) - shell: python - - - name: Get SonarCloud Status - id: get-sonarcloud-status - run: | - sonarCloudProjectStatus=$(curl -s -u "${{ secrets.sonarCloudToken }}:" "https://sonarcloud.io/api/qualitygates/project_status?projectKey=${{ inputs.sonarCloudProjectName }}") - - # Check if the response is empty or not valid JSON - if [ -z "$sonarCloudProjectStatus" ] || ! echo "$sonarCloudProjectStatus" | jq . > /dev/null 2>&1; then - echo "Error: The SONAR_TOKEN is invalid, expired, or the response is empty. Please check: https://sonarcloud.io/account/security and update your token: https://github.com/${{ github.repository }}/settings/secrets/actions" >&2 - echo "Returned response: $sonarCloudProjectStatus" >&2 - exit 1 - fi - - # Output the JSON response if valid - echo "Returned response: $sonarCloudProjectStatus" - echo "sonarCloudProjectStatus=$sonarCloudProjectStatus" >> $GITHUB_OUTPUT - continue-on-error: false - shell: bash - - - name: Trigger Initial Analysis - if: ${{ fromJson(steps.get-sonarcloud-status.outputs.sonarCloudProjectStatus).projectStatus.status == 'NONE' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.sonarCloudToken }} - run: | - dotnet sonarscanner begin /k:"${{ inputs.sonarCloudProjectName }}" /o:"${{ env.lowerCaseOwner }}" /d:sonar.token="${{ secrets.sonarCloudToken }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" /d:sonar.cs.vstest.reportsPaths="**/TestResults/**.trx" - dotnet build "${{ steps.findSlnFile.outputs.solutionFilePath }}" ` - -p:GenerateDataMinerPackage=false ` - --configuration ${{ inputs.configuration }} ` - -nodeReuse:false - dotnet sonarscanner end /d:sonar.token="${{ secrets.sonarCloudToken }}" - continue-on-error: true - shell: pwsh - - - name: Start Analysis - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.sonarCloudToken }} - run: | - dotnet sonarscanner begin /k:"${{ inputs.sonarCloudProjectName }}" /o:"${{ env.lowerCaseOwner }}" /d:sonar.token="${{ secrets.sonarCloudToken }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" /d:sonar.cs.vstest.reportsPaths="**/TestResults/**.trx" - continue-on-error: true - - - name: Build for pre-release - if: inputs.referenceType == 'branch' - env: - DATAMINER_TOKEN: ${{ secrets.dataminerToken }} - OVERRIDE_CATALOG_DOWNLOAD_TOKEN: ${{ secrets.overrideCatalogDownloadToken }} - run: | - dotnet build "${{ steps.findSlnFile.outputs.solutionFilePath }}" ` - -p:Version="0.0.${{ inputs.runNumber }}" ` - --configuration ${{ inputs.configuration }} ` - -p:CatalogPublishKeyName="DATAMINER_TOKEN" ` - -p:CatalogDefaultDownloadKeyName="OVERRIDE_CATALOG_DOWNLOAD_TOKEN" ` - -p:SkylineDataMinerSdkDebug="${{ inputs.debug }}" ` - -nodeReuse:false - shell: pwsh - - - name: Build for release - if: inputs.referenceType == 'tag' - env: - DATAMINER_TOKEN: ${{ secrets.dataminerToken }} - OVERRIDE_CATALOG_DOWNLOAD_TOKEN: ${{ secrets.overrideCatalogDownloadToken }} - run: | - dotnet build "${{ steps.findSlnFile.outputs.solutionFilePath }}" ` - -p:Version="${{ inputs.referenceName }}" ` - --configuration ${{ inputs.configuration }} ` - -p:CatalogPublishKeyName="DATAMINER_TOKEN" ` - -p:CatalogDefaultDownloadKeyName="OVERRIDE_CATALOG_DOWNLOAD_TOKEN" ` - -p:SkylineDataMinerSdkDebug="${{ inputs.debug }}" ` - -nodeReuse:false - shell: pwsh - - - name: Unit Tests - # when not using MSTest you'll need to install coverlet.collector nuget in your test solutions - id: unit-tests - run: dotnet test "${{ steps.findSlnFile.outputs.solutionFilePath }}" --no-build --configuration ${{ inputs.configuration }} --filter TestCategory!=IntegrationTest --logger "trx;logfilename=unitTestResults.trx" --collect "XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura,opencover - - - name: Stop Analysis - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.sonarCloudToken }} - run: | - dotnet sonarscanner end /d:sonar.token="${{ secrets.sonarCloudToken }}" - continue-on-error: true - - - name: SonarCloud Quality Gate check - if: inputs.referenceType == 'branch' - id: sonarcloud-quality-gate-check - uses: sonarsource/sonarqube-quality-gate-action@master - with: - scanMetadataReportFile: .sonarqube/out/.sonar/report-task.txt - timeout-minutes: 5 - env: - SONAR_TOKEN: ${{ secrets.sonarCloudToken }} - - - name: Create package name - if: inputs.referenceType == 'tag' - id: packageName - run: | - $tempName = "${{ inputs.repository }}" - $safeName = $tempName -replace '[\"\/\\<>|:*?]', '_' - echo "name=$safeName" >> $env:GITHUB_OUTPUT - shell: pwsh - - - name: Generate SBOM file - if: inputs.referenceType == 'tag' - run: | - find . -type f -name "*.dmapp" -print0 | while IFS= read -r -d '' file; do - echo "Generating SBOM for $file" - dataminer-sbom generate-and-add \ - --solution-path "${{ steps.findSlnFile.outputs.solutionFilePath }}" \ - --package-file "$file" \ - --package-name "${{ steps.packageName.outputs.name }}" \ - --package-version "${{ inputs.referenceName }}" \ - --package-supplier "Skyline Communications" \ - --debug "${{ inputs.debug }}" - done - - - name: Remove WebApiLib dll - run: | - find . -type f -name "*.dmapp" -print0 | while IFS= read -r -d '' file; do - echo "Updating $file" - - # Create a temp directory - temp_dir=$(mktemp -d) - - # Unzip the .dmapp file into the temp dir - unzip -q "$file" -d "$temp_dir" - - echo "📂 Files before removal:" - find "$temp_dir" -type f - - # Remove the specific line from Description.txt (if it exists) - desc_file=$(find "$temp_dir" -type f -name "Description.txt") - if [[ -f "$desc_file" ]]; then - echo "📝 Cleaning Description.txt at $desc_file" - sed -i '/ProtocolScripts\\DllImport\\WebApiLib\.dll/d' "$desc_file" - fi - - # Find and delete all files ending in WebApiLib.dll - find "$temp_dir" -type f -name "*WebApiLib.dll" -exec rm -f {} \; - - echo "📂 Files after removal:" - find "$temp_dir" -type f - - # Recreate the .dmapp file - (cd "$temp_dir" && zip -qr "../temp.dmapp" .) - - # Move the new archive back to the original file - mv -f "$temp_dir/../temp.dmapp" "$file" - - # Cleanup - rm -rf "$temp_dir" - - echo "Cleaned $file" - - done - - - uses: actions/upload-artifact@v4 - with: - name: DataMiner Installation Packages (${{ inputs.configuration }} ${{ inputs.solutionFilterName }}) - path: | - **/bin/${{ inputs.configuration }}/*.dmapp - **/bin/${{ inputs.configuration }}/*.zip - **/bin/${{ inputs.configuration }}/**/*.dmapp - **/bin/${{ inputs.configuration }}/**/*.zip - continue-on-error: true - - - name: Authenticate with GitHub CLI - if: inputs.referenceType == 'tag' - run: gh auth login --with-token <<< "${{ secrets.GITHUB_TOKEN }}" - - - name: Find Version Comment - if: inputs.referenceType == 'tag' - id: findVersionComment - run: | - echo "Checking for release notes associated with the reference: '${{ inputs.referenceName }}'" - - # Retrieve the release note body - RELEASE_NOTE=$(gh release view "${{ inputs.referenceName }}" --json body -q '.body' 2>/dev/null || echo "") - - escape_special_chars() { - echo "$1" | sed -e 's/,/%2c/g' -e 's/"/%22/g' -e 's/;/%3b/g' - } - - if [[ -n "$RELEASE_NOTE" ]]; then - ESCAPED_RELEASE_NOTE=$(escape_special_chars "$RELEASE_NOTE") - echo "Release note found for '${{ inputs.referenceName }}': $ESCAPED_RELEASE_NOTE" - # Escape multiline string for GITHUB_OUTPUT - echo "versionComment<> $GITHUB_OUTPUT - echo "$ESCAPED_RELEASE_NOTE" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - else - echo "No release note found for '${{ inputs.referenceName }}'. Falling back to tag or commit message." - VERSION_COMMENT=$(git describe --tags --exact-match 2>/dev/null || git log -1 --pretty=format:%s) - ESCAPED_VERSION_COMMENT=$(escape_special_chars "$VERSION_COMMENT") - echo "Fallback version comment: $ESCAPED_VERSION_COMMENT" - # Escape fallback as well - echo "versionComment=$ESCAPED_VERSION_COMMENT" >> $GITHUB_OUTPUT - fi - shell: bash - - - name: Find branch - if: inputs.referenceType == 'tag' - id: findBranch - run: | - #!/bin/bash - set -e # Exit immediately if a command exits with a non-zero status. - - # Capture the branches containing the tag and process them - branches="$(git branch --contains tags/${{ inputs.referenceName }} -r | grep 'origin/' | grep -vE '.*/.*/' | sed 's#origin/##' | paste -sd ",")" - - # Append to GitHub Actions output - echo "branch=${branches}" >> $GITHUB_OUTPUT - shell: bash - - - name: Target Branch - if: inputs.referenceType == 'tag' - id: showResultBranch - run: echo "${{ steps.findBranch.outputs.branch }}" - - - name: Find Installation package - if: inputs.referenceType == 'tag' - id: findInstallationPackage - run: | - IFS=$'\n' - echo dmappPackageName=$(find '${{ github.workspace }}/Low Code App Editor Package' -type f -name '*.dmapp') >> $GITHUB_OUTPUT - unset IFS - shell: bash - - - name: Target Installation Package - if: inputs.referenceType == 'tag' - id: showResultPackage - run: echo "${{ steps.findInstallationPackage.outputs.dmappPackageName }}" - - - name: Publish To Catalog (Metadata Only) - if: inputs.referenceType == 'branch' && inputs.referenceName == 'main' - shell: pwsh - run: | - dataminer-catalog-upload update-catalog-details ` - --path-to-catalog-yml "${{ github.workspace }}/Low Code App Editor Package/CatalogInformation/manifest.yml" ` - --path-to-readme "${{ github.workspace }}/Low Code App Editor Package/CatalogInformation/README.md" ` - --path-to-images "${{ github.workspace }}/Low Code App Editor Package/CatalogInformation/Images" ` - --dm-catalog-token "${{ secrets.dataminerToken }}" ` - --debug "${{ inputs.debug }}" - - - name: Publish To Catalog - if: inputs.referenceType == 'tag' - env: - DATAMINER_TOKEN: ${{ secrets.dataminerToken }} - shell: pwsh - run: | - dataminer-catalog-upload with-registration ` - --path-to-catalog-yml "${{ github.workspace }}/Low Code App Editor Package/CatalogInformation/manifest.yml" ` - --path-to-readme "${{ github.workspace }}/Low Code App Editor Package/CatalogInformation/README.md" ` - --path-to-images "${{ github.workspace }}/Low Code App Editor Package/CatalogInformation/Images" ` - --dm-catalog-token "${{ secrets.dataminerToken }}" ` - --path-to-artifact "${{ steps.findInstallationPackage.outputs.dmappPackageName }}" ` - --artifact-version ${{ inputs.referenceName }} ` - --branch "${{ steps.findBranch.outputs.branch }}" ` - --debug "${{ inputs.debug }}" \ No newline at end of file diff --git a/.github/workflows/complete.yml b/.github/workflows/complete.yml index e186a6c..9607516 100644 --- a/.github/workflows/complete.yml +++ b/.github/workflows/complete.yml @@ -4,7 +4,8 @@ name: Skyline Reusable Quality Workflow on: # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [] + branches: + - '**' tags: - "[0-9]+.[0-9]+.[0-9]+.[0-9]+" - "[0-9]+.[0-9]+.[0-9]+.[0-9]+-**" @@ -18,17 +19,11 @@ on: jobs: CI: - uses: SkylineCommunications/Low-Code-App-Editor/.github/workflows/DataMiner App Packages Master Workflow Custom.yml@main + uses: SkylineCommunications/_ReusableWorkflows/.github/workflows/Master Workflow.yml@main with: - configuration: Release - referenceName: ${{ github.ref_name }} - runNumber: ${{ github.run_number }} - referenceType: ${{ github.ref_type }} - repository: ${{ github.repository }} - owner: ${{ github.repository_owner }} - sonarCloudProjectName: ${{ vars.SONAR_NAME }} # Go to 'https://sonarcloud.io/projects/create' and create a project. Then create a SONAR_NAME variable with the ID of the project as mentioned in the SonarCloud project URL. - # solutionFilterName: "MySolutionFilter.slnf" + sonarcloud-project-name: ${{ vars.SONAR_NAME }} # Go to 'https://sonarcloud.io/projects/create' and create a project. Then create a SONAR_NAME variable with the ID of the project as mentioned in the SonarCloud project URL. + # solution-filter-name: "MySolutionFilter.slnf" secrets: - dataminerToken: ${{ secrets.DATAMINER_TOKEN }} # The API key: generated in the DCP Admin app (https://admin.dataminer.services/) as authentication for a certain organization. - sonarCloudToken: ${{ secrets.SONAR_TOKEN }} # The API key for access to SonarCloud. - # overrideCatalogDownloadToken: ${{ secrets.OVERRIDE_DATAMINER_TOKEN }} # Override on the dataminerToken for downloading Catalog items: generated in the DCP Admin app (https://admin.dataminer.services/) as authentication for a certain organization. \ No newline at end of file + DATAMINER_TOKEN: ${{ secrets.DATAMINER_TOKEN }} # The API key: generated in the DCP Admin app (https://admin.dataminer.services/) as authentication for a certain organization. + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # The API key for access to SonarCloud. + # OVERRIDE_CATALOG_DOWNLOAD_TOKEN: ${{ secrets.OVERRIDE_DATAMINER_TOKEN }} # Override on the dataminerToken for downloading Catalog items: generated in the DCP Admin app (https://admin.dataminer.services/) as authentication for a certain organization. \ No newline at end of file diff --git a/Documentation/CHANGELOG_1.0.1-CU20.md b/Documentation/CHANGELOG_1.0.1-CU20.md new file mode 100644 index 0000000..a010152 --- /dev/null +++ b/Documentation/CHANGELOG_1.0.1-CU20.md @@ -0,0 +1,2 @@ +**New Feature** +- New option to include or exclude the security config from the export. (not included by default) \ No newline at end of file diff --git a/Low Code App Editor Installer/Low Code App Editor Installer.csproj b/Low Code App Editor Installer/Low Code App Editor Installer.csproj index ca84057..d0c628b 100644 --- a/Low Code App Editor Installer/Low Code App Editor Installer.csproj +++ b/Low Code App Editor Installer/Low Code App Editor Installer.csproj @@ -3,6 +3,7 @@ net48 true Install_1 + true AutomationScript @@ -15,7 +16,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Low Code App Editor Package/CatalogInformation/Images/ExportDialog.png b/Low Code App Editor Package/CatalogInformation/Images/ExportDialog.png index dc668da..7d32f5d 100644 Binary files a/Low Code App Editor Package/CatalogInformation/Images/ExportDialog.png and b/Low Code App Editor Package/CatalogInformation/Images/ExportDialog.png differ diff --git a/Low Code App Editor Package/Low Code App Editor Package.cs b/Low Code App Editor Package/Low Code App Editor Package.cs index c9b70d8..e09898a 100644 --- a/Low Code App Editor Package/Low Code App Editor Package.cs +++ b/Low Code App Editor Package/Low Code App Editor Package.cs @@ -83,8 +83,6 @@ public void Install(Engine engine, AppInstallContext context) var installer = new AppInstaller(Engine.SLNetRaw, context); installer.InstallDefaultContent(); - // Custom installation logic can be added here for each individual install package. - // Create a symbolic link to the WebApiLib.dll Action logger = installer.Log; Engine.SLNetRaw.CreateSymbolicLink(WebApiLib_ProtocolScripts_Path, WebApiLib_WebPages_Path, logger); diff --git a/Low Code App Editor Package/Low Code App Editor Package.csproj b/Low Code App Editor Package/Low Code App Editor Package.csproj index 55191d4..3c8d28e 100644 --- a/Low Code App Editor Package/Low Code App Editor Package.csproj +++ b/Low Code App Editor Package/Low Code App Editor Package.csproj @@ -16,7 +16,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -25,4 +25,46 @@ true + + + + + + + + + + + + string.Equals(e.FullName.Replace('\\', '/'), normalizedEntry, System.StringComparison.OrdinalIgnoreCase)); + if (entry != null) + { + entry.Delete(); + Log.LogMessage(MessageImportance.High, "Removed '" + entry.FullName + "' from " + ZipFilePath); + } + else + { + Log.LogMessage(MessageImportance.High, "Entry '" + EntryPath + "' not found in zip, nothing to remove."); + } + } + ]]> + + + + + + + $(MSBuildProjectDirectory)\$(OutputPath)DataMinerBuild\$(AssemblyName).$(Version).dmapp + + + \ No newline at end of file diff --git a/Low Code App Editor/Controllers/ExportController.cs b/Low Code App Editor/Controllers/ExportController.cs index b6caf21..78462a9 100644 --- a/Low Code App Editor/Controllers/ExportController.cs +++ b/Low Code App Editor/Controllers/ExportController.cs @@ -27,6 +27,7 @@ public class ExportController { public static readonly string ScriptPath = @"C:\Skyline DataMiner\Scripts"; public static readonly string DllImportPath = @"C:\Skyline DataMiner\ProtocolScripts\DllImport"; + public static readonly string SolutionLibrariesPath = @"C:\Skyline DataMiner\ProtocolScripts\DllImport\SolutionLibraries"; public static readonly string LowCodeAppEditorPath = @"C:\Skyline DataMiner\Documents\Low Code App Editor"; public static readonly string LowCodeAppEditorExportPath = @"C:\Skyline DataMiner\Documents\DMA_COMMON_DOCUMENTS\Low Code Apps Exports"; public static readonly string ThemesPath = @"C:\Skyline DataMiner\dashboards\Themes.json"; @@ -47,7 +48,7 @@ private static string ExportPackage(IEngine engine, IEnumerable apps, Expor exportPath = Path.Combine(LowCodeAppEditorExportPath, $"{apps.First().Name}_{now.ToString("yyyy-MM-dd HH-mm-ss")}_App_Export.zip"); } - engine.GenerateInformation($"Export Path: {exportPath}"); + engine.Log($"Export Path: {exportPath}"); if (!Directory.Exists(Path.GetDirectoryName(exportPath))) { @@ -57,7 +58,7 @@ private static string ExportPackage(IEngine engine, IEnumerable apps, Expor using (var fs = new FileStream(exportPath, FileMode.Create)) using (var zip = new ZipArchive(fs, ZipArchiveMode.Create)) { - engine.GenerateInformation($"Adding Package Information"); + engine.Log($"Adding Package Information"); // Package Information var info = apps.Count() == 1 ? PackageInfo.FromApp(apps.First()) : PackageInfo.FromApp(); @@ -85,25 +86,25 @@ private static string ExportPackage(IEngine engine, IEnumerable apps, Expor var themes = new List(); foreach (var app in apps) { - engine.GenerateInformation($"Adding App"); + engine.Log($"Adding App"); // Add the app as CompanionFiles AddAppToArchive(zip, app, options); - if (options.ExcludeScripts) + if (!options.ExcludeScripts) { - engine.GenerateInformation("Skipping Automation Scripts"); + engine.Log($"Adding Scripts"); + AddScriptsToArchive(zip, app); } else { - engine.GenerateInformation($"Adding Scripts"); - AddScriptsToArchive(zip, app); + engine.Log("Skipping Automation Scripts"); } if (!options.ExcludeDom) { // Add Dom definitions - engine.GenerateInformation($"Adding DOM modules, for app '{app.Name}'"); + engine.Log($"Adding DOM modules, for app '{app.Name}'"); domModuleIds.AddRangeUnique(app.LatestVersion.GetUsedDomModules()); AddDomToArchive(engine, zip, domModuleIds, options); } @@ -111,7 +112,7 @@ private static string ExportPackage(IEngine engine, IEnumerable apps, Expor if (!options.ExcludeImages) { // Add Images to companion files - engine.GenerateInformation($"Adding Images, for app '{app.Name}'"); + engine.Log($"Adding Images, for app '{app.Name}'"); images.AddRangeUnique(app.LatestVersion.GetUsedImages()); AddImagesToArchive(zip, images); } @@ -119,13 +120,13 @@ private static string ExportPackage(IEngine engine, IEnumerable apps, Expor if (!options.ExcludeThemes) { // Add Theme - engine.GenerateInformation($"Adding Themes, for app '{app.Name}'"); + engine.Log($"Adding Themes, for app '{app.Name}'"); themes.AddRangeUnique(app.LatestVersion.GetUsedThemes()); AddThemesToArchive(zip, themes); } } - engine.GenerateInformation($"Adding Installer code"); + engine.Log($"Adding Installer code"); // Add custom Low Code App Installer Code zip.CreateEntryFromDirectory(LowCodeAppEditorPath, "Scripts"); @@ -148,7 +149,7 @@ private static string ExportPackage(IEngine engine, IEnumerable apps, Expor sb.AppendLine($"Script\\{Path.GetDirectoryName(file.FullName)}"); } - foreach(var dependency in zip.GetEntries("AppInstallContent\\Assemblies").Where(x => !x.FullName.EndsWith("\\"))) + foreach (var dependency in zip.GetEntries("AppInstallContent\\Assemblies").Where(x => !x.FullName.EndsWith("\\"))) { sb.AppendLine(dependency.FullName); } @@ -178,10 +179,39 @@ private static void AddAppToArchive(ZipArchive zip, App app, ExportOptions optio app = app ?? throw new ArgumentNullException(nameof(app)); options = options ?? throw new ArgumentNullException(nameof(options)); + if (!options.IncludeSecuritySettings) + { + // Strip the security settings from the app settings file before adding to the archive + var appSettingsPath = Path.Combine(app.Path, "App.info.json"); + var appSettings = JObject.Parse(File.ReadAllText(appSettingsPath)); + if (appSettings.TryGetValue("Security", out var securityToken) && + securityToken is JObject securitySettings) + { + if (securitySettings.TryGetValue("AllowEdit", out var allowEditToken) && + allowEditToken is JArray allowEdits && + allowEdits.Count > 0) + { + allowEdits.Clear(); + } + + if (securitySettings.TryGetValue("AllowView", out var allowViewToken) && + allowViewToken is JArray allowViews && + allowViews.Count > 0) + { + allowViews.Clear(); + } + } + + zip.CreateEntryFromText(Path.Combine("AppInstallContent", "CompanionFiles", "LCA", app.LatestVersion.ID, "App.info.json"), appSettings.ToString(Formatting.None, Array.Empty())); + } + else + { + zip.CreateEntryFromFile(app.PathSettings, Path.Combine("AppInstallContent", "CompanionFiles", "LCA", app.LatestVersion.ID, "App.info.json")); + } + if (!options.IncludeVersions) { // Just include the general .json file and the latest version - zip.CreateEntryFromDirectory(app.Path, Path.Combine("AppInstallContent", "CompanionFiles", "LCA", app.LatestVersion.ID), false); zip.CreateEntryFromDirectory(Path.Combine(app.Path, $"version_{app.LatestVersion.Version} "), Path.Combine("AppInstallContent", "CompanionFiles", "LCA", app.LatestVersion.ID, $"version_{app.LatestVersion.Version}"), true); if (app.LatestDraftVersion != null) zip.CreateEntryFromDirectory(Path.Combine(app.Path, $"version_{app.LatestDraftVersion.Version} "), Path.Combine("AppInstallContent", "CompanionFiles", "LCA", app.LatestDraftVersion.ID, $"version_{app.LatestDraftVersion.Version}"), true); @@ -189,7 +219,10 @@ private static void AddAppToArchive(ZipArchive zip, App app, ExportOptions optio else { // Include everything - zip.CreateEntryFromDirectory(app.Path, Path.Combine("AppInstallContent", "CompanionFiles", "LCA", app.LatestVersion.ID)); + foreach (var directory in Directory.GetDirectories(app.Path, "version_*")) + { + zip.CreateEntryFromDirectory(directory, Path.Combine("AppInstallContent", "CompanionFiles", "LCA", app.LatestVersion.ID, Path.GetFileNameWithoutExtension(directory)), true); + } } } @@ -209,7 +242,7 @@ private static void AddScriptToArchive(ZipArchive zip, App app, string script, L var scriptPath = Path.Combine(ScriptPath, scriptName); if (File.Exists(scriptPath)) { - if(addedFiles.Exists(x => x == $"AppInstallContent\\Scripts\\{script}\\Script_{script}.xml")) + if (addedFiles.Exists(x => x == $"AppInstallContent\\Scripts\\{script}\\Script_{script}.xml")) { return; } @@ -262,12 +295,18 @@ private static void AddDependenciesToArchive(ZipArchive zip, App app, string scr var refParams = doc.Descendants(ns + "Param").Where(param => (string)param.Attribute("type") == "ref"); foreach (var reference in refParams.Select(refParam => refParam.Value)) { + if (reference.StartsWith(SolutionLibrariesPath, StringComparison.InvariantCultureIgnoreCase)) + { + // DevPacks should be excluded + continue; + } + if (addedFiles.Exists(x => x == reference.Replace(@"C:\Skyline DataMiner", "AppInstallContent\\Assemblies"))) { continue; } - if (reference.StartsWith(DllImportPath)) + if (reference.StartsWith(DllImportPath, StringComparison.InvariantCultureIgnoreCase)) { zip.CreateEntryFromFile(reference, reference.Replace(@"C:\Skyline DataMiner", "AppInstallContent\\Assemblies")); addedFiles.Add(reference.Replace(@"C:\Skyline DataMiner", "AppInstallContent\\Assemblies")); @@ -347,6 +386,8 @@ public class ExportOptions public bool OverwriteThemes { get; set; } + public bool IncludeSecuritySettings { get; set; } + public static ExportOptions FromDialog(ExportDialog dialog) { return new ExportOptions @@ -361,6 +402,9 @@ public static ExportOptions FromDialog(ExportDialog dialog) SyncImages = dialog.SyncImages.IsChecked, ExcludeThemes = dialog.ExcludeThemes.IsChecked, SyncThemes = dialog.SyncThemes.IsChecked, + OverrideImages = dialog.OverwriteImages.IsChecked, + OverwriteThemes = dialog.OverwriteThemes.IsChecked, + IncludeSecuritySettings = dialog.IncludeSecuritySettings.IsChecked, }; } } diff --git a/Low Code App Editor/LCA/App.cs b/Low Code App Editor/LCA/App.cs index a0eb0df..33bc493 100644 --- a/Low Code App Editor/LCA/App.cs +++ b/Low Code App Editor/LCA/App.cs @@ -11,6 +11,7 @@ namespace Low_Code_App_Editor.LCA using Newtonsoft.Json; + using Skyline.DataMiner.Utils.SecureCoding.SecureSerialization.Json.Newtonsoft; using Skyline.DataMiner.Web.Common.v1; public class App @@ -24,14 +25,13 @@ public class App }, }; - public App(string path) { Path = path; // Load general settings file var settingsFile = File.ReadAllText(System.IO.Path.Combine(path, "App.info.json")); - Settings = JsonConvert.DeserializeObject(settingsFile); + Settings = SecureNewtonsoftDeserialization.DeserializeObject(settingsFile); // Load version history var versions = new string[0]; @@ -49,7 +49,7 @@ public App(string path) var versionPath = System.IO.Path.Combine(versionDirectory, "App.config.json"); if (File.Exists(versionPath)) { - var version = JsonConvert.DeserializeObject(File.ReadAllText(versionPath), settings); + var version = SecureNewtonsoftDeserialization.DeserializeObject(File.ReadAllText(versionPath), settings); version.Path = versionPath; Versions.Add(version); } @@ -65,7 +65,7 @@ public App(string path) var versionPath = System.IO.Path.Combine(versionDirectory, "App.config.json"); if (File.Exists(versionPath)) { - var version = JsonConvert.DeserializeObject(File.ReadAllText(versionPath), settings); + var version = SecureNewtonsoftDeserialization.DeserializeObject(File.ReadAllText(versionPath), settings); version.Path = versionPath; Versions.Add(version); } diff --git a/Low Code App Editor/Low Code App Editor.cs b/Low Code App Editor/Low Code App Editor.cs index 8c60a7f..0afa5cb 100644 --- a/Low Code App Editor/Low Code App Editor.cs +++ b/Low Code App Editor/Low Code App Editor.cs @@ -45,10 +45,11 @@ Ambachtenstraat 33 DATE VERSION AUTHOR COMMENTS -02/12/2024 1.0.0.16 AMA, Skyline Changed some of the names of the Export Dialog to be more readable. -27/12/2024 1.0.0.17 AMA, Skyline Added extra check before casting to DMADashboardQueryData when exporting apps. -10/04/2025 1.0.0.18 AMA, Skyline Fixed bug where you could import pages/panels from your own app. Added duplicate functionality for pages/panels. -10/04/2025 1.0.0.19 AMA, Skyline Fixed bug when deserializing to dynamic properties. Expanded the DOM module search in queries to also look in joins +05/12/2024 1.0.1-CU15 AMA, Skyline Changed some of the names of the Export Dialog to be more readable. +27/12/2024 1.0.1-CU16 AMA, Skyline Added extra check before casting to DMADashboardQueryData when exporting apps. +10/04/2025 1.0.1-CU17 AMA, Skyline Fixed bug where you could import pages/panels from your own app. Added duplicate functionality for pages/panels. +06/05/2025 1.0.1-CU18 AMA, Skyline Fixed bug when deserializing to dynamic properties. Expanded the DOM module search in queries to also look in joins +14/05/2025 1.0.1-CU19 AMA, Skyline Installer will now create a symbolic link to the WebApiLib.dll **************************************************************************** */ diff --git a/Low Code App Editor/Low Code App Editor.csproj b/Low Code App Editor/Low Code App Editor.csproj index dcb6a86..f30dffc 100644 --- a/Low Code App Editor/Low Code App Editor.csproj +++ b/Low Code App Editor/Low Code App Editor.csproj @@ -16,7 +16,8 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Low Code App Editor/Low Code App Editor.xml b/Low Code App Editor/Low Code App Editor.xml index 2107e78..9634474 100644 --- a/Low Code App Editor/Low Code App Editor.xml +++ b/Low Code App Editor/Low Code App Editor.xml @@ -23,7 +23,7 @@ System.Xml.Linq.dll System.IO.Compression.dll System.IO.Compression.FileSystem.dll - C:\Skyline DataMiner\Webpages\API\bin\WebApiLib.dll + C:\Skyline DataMiner\ProtocolScripts\WebApiLib.dll \ No newline at end of file diff --git a/Low Code App Editor/UI/ExportDialog.cs b/Low Code App Editor/UI/ExportDialog.cs index b1972d9..529ea1f 100644 --- a/Low Code App Editor/UI/ExportDialog.cs +++ b/Low Code App Editor/UI/ExportDialog.cs @@ -38,10 +38,12 @@ public ExportDialog(IEngine engine) : base(engine) Panel.Add(SyncThemes, 11, 1); Panel.Add(new Label("Overwrite Themes:"), 12, 0); Panel.Add(OverwriteThemes, 12, 1); - Panel.Add(new WhiteSpace(), 13, 0); - Panel.Add(BackButton, 14, 0); - Panel.Add(ExportButton, 14, 1); - Panel.Add(Status, 15, 0, 1, 3); + Panel.Add(new Label("Include Security Settings:"), 13, 0); + Panel.Add(IncludeSecuritySettings, 13, 1); + Panel.Add(new WhiteSpace(), 14, 0); + Panel.Add(BackButton, 15, 0); + Panel.Add(ExportButton, 15, 1); + Panel.Add(Status, 16, 0, 1, 3); } public ICheckBoxList Apps { get; } = new CheckBoxList(); @@ -70,6 +72,8 @@ public ExportDialog(IEngine engine) : base(engine) public ICheckBox OverwriteThemes { get; } = new CheckBox { IsChecked = true }; + public ICheckBox IncludeSecuritySettings { get; } = new CheckBox(); + public IButton BackButton { get; } = new Button("Back"); public IButton ExportButton { get; } = new Button("Export"); diff --git a/Low Code App EditorTests/Low Code App EditorTests.csproj b/Low Code App EditorTests/Low Code App EditorTests.csproj index 547b4c0..f83de98 100644 --- a/Low Code App EditorTests/Low Code App EditorTests.csproj +++ b/Low Code App EditorTests/Low Code App EditorTests.csproj @@ -11,10 +11,10 @@ - - - - + + + + diff --git a/README.md b/README.md index 80fa654..b9942b1 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,9 @@ This options will sync the themes.json file across the DMS This options will overwrite the existing themes if there are already matching themes found. The matching is based on the theme name. +##### 13. Include Security Settings + +When enabled this will keep the Security settings of the app. By default it is disabled because the editor cannot be sure the exact users and groups exists on the importing system. In case you know that both systems match, then you can enable it and the exporter will keep the settings as they are. #### Overview of included items - Custom operators diff --git a/global.json b/global.json index 628c869..6e740d6 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "Skyline.DataMiner.Sdk": "2.4.6" + "Skyline.DataMiner.Sdk": "2.5.2" } } \ No newline at end of file