From edb61b51ee7264a3d9bef29014af204088302a47 Mon Sep 17 00:00:00 2001 From: chensation <32966177+chensation@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:58:54 -0400 Subject: [PATCH 01/26] Remove operations to modify arm managed resources (#872) * added warnings; missing data from request * add app and service * changed warning color * action modal wip * moved action-dialog into module * warning modal * modal changes, allowed isolatedActions to use action dialog * completed modal changes * fixed name error * appType modal complete * warning in appType complete * fix typos * fix typo x2 * add tooltip to link; change link name to view * added filter and cleaned up display when no link * changed arm managed list setting to be it's own custom component * wip: add arm managed to applications page * add link to navbar warning * add base to applicastions -> add warning banner to applications * removed all actions from arm-managed entities except scale service and unprovision all; modified how warning modal works * change wording for the messages * change messaging * remove scale service * removed dev testing change * add tests * reworked apptype-viewer to be reused by apptype and apps; apps now check for apptype as well * added temp banner * changed warning banner phrasing * removed add service for arm-managed app; added view link to apptype && app * added link to public doc * fix service test on arm managed * cleaned up and made adjustments based on pr feedback --- src/SfxWeb/cypress/e2e/app.cy.js | 11 ++- src/SfxWeb/cypress/e2e/appType.cy.js | 51 ++++++++---- src/SfxWeb/cypress/e2e/apps.cy.js | 4 +- src/SfxWeb/cypress/e2e/cluster.cy.js | 2 +- src/SfxWeb/cypress/e2e/service.cy.js | 30 +++++++- .../fixtures/app-page/app-arm-managed.json | 18 +++++ .../app-page/services-arm-managed.json | 36 +++++++++ src/SfxWeb/cypress/fixtures/appType.json | 32 +++++++- src/SfxWeb/cypress/fixtures/applications.json | 43 ++++++++--- .../service-page/service-arm-managed.json | 16 ++++ .../service-stateless-arm-managed.json | 15 ++++ src/SfxWeb/src/Styles/_modal.scss | 6 ++ src/SfxWeb/src/Styles/_vars.scss | 4 + src/SfxWeb/src/app/Models/Action.ts | 22 +++--- .../src/app/Models/DataModels/Application.ts | 32 ++++++-- .../app/Models/DataModels/ApplicationType.ts | 69 ++++++++++++++--- .../Models/DataModels/DeployedCodePackage.ts | 12 ++- .../app/Models/DataModels/DeployedReplica.ts | 12 ++- src/SfxWeb/src/app/Models/DataModels/Node.ts | 62 +++++++++++---- .../src/app/Models/DataModels/Replica.ts | 24 ++++-- .../src/app/Models/DataModels/Service.ts | 77 +++++++++++++------ .../DataModels/collections/Collections.ts | 4 + src/SfxWeb/src/app/Models/ListSettings.ts | 16 +++- src/SfxWeb/src/app/Models/RawDataTypes.ts | 21 ++++- src/SfxWeb/src/app/ViewModels/Modal.ts | 20 ++++- src/SfxWeb/src/app/app.module.ts | 4 +- .../action-dialog/DialogBodyComponent.ts | 8 ++ .../action-dialog/action-dialog.module.ts | 19 +++++ .../action-dialog.component.html | 12 +++ .../action-dialog.component.scss | 0 .../action-dialog.component.spec.ts | 0 .../action-dialog/action-dialog.component.ts | 52 +++++++++++++ .../action-dialog/dialog-body.directive.ts | 10 +++ .../message-with-confirmation.component.html | 4 + .../message-with-confirmation.component.scss | 0 ...essage-with-confirmation.component.spec.ts | 25 ++++++ .../message-with-confirmation.component.ts | 31 ++++++++ .../message-with-warning.component.html | 6 ++ .../message-with-warning.component.scss | 0 .../message-with-warning.component.spec.ts | 25 ++++++ .../message-with-warning.component.ts | 38 +++++++++ .../src/app/modules/action-dialog/utils.ts | 17 ++++ .../dashboard-tile.component.ts | 6 +- .../arm-managed/arm-managed.component.html | 4 + .../arm-managed/arm-managed.component.scss | 0 .../arm-managed/arm-managed.component.spec.ts | 25 ++++++ .../arm-managed/arm-managed.component.ts | 25 ++++++ .../detail-list-templates.module.ts | 3 +- .../hyper-link/hyper-link.component.html | 7 +- .../hyper-link/hyper-link.component.ts | 12 ++- .../row-display/row-display.component.ts | 6 +- .../folder-actions.component.ts | 12 ++- .../powershell-commands.component.ts | 19 +++-- .../tree/tree-view/tree-view.component.html | 6 +- .../upgrade-progress.component.ts | 2 +- .../src/app/services/settings.service.ts | 17 ++-- .../action-dialog.component.html | 15 ---- .../action-dialog/action-dialog.component.ts | 29 ------- .../arm-warning/arm-warning.component.html | 6 ++ .../arm-warning/arm-warning.component.scss | 0 .../arm-warning/arm-warning.component.spec.ts | 25 ++++++ .../arm-warning/arm-warning.component.ts | 13 ++++ .../component/navbar/navbar.component.html | 1 + src/SfxWeb/src/app/shared/shared.module.ts | 11 +-- .../action-row/action-row.component.html | 5 ++ .../action-row/action-row.component.scss | 4 + .../application-type/base/base.component.html | 4 +- .../create-application.component.html | 22 ++---- .../create-application.component.ts | 38 +++++---- .../essentials/essentials.component.ts | 3 +- .../application/backup/backup.component.ts | 28 +++++-- .../application/base/base.component.html | 4 +- .../essentials/essentials.component.ts | 20 +++-- .../views/applications/all/all.component.ts | 26 ++----- .../views/applications/applicationsBase.ts | 2 + .../applications/base/base.component.html | 4 +- .../views/applications/base/base.component.ts | 10 ++- .../commands/commands.component.ts | 12 +-- .../applications/events/events.component.ts | 12 ++- .../upgrading/upgrading.component.ts | 31 +++----- .../status-warnings.component.scss | 5 +- .../status-warnings.component.ts | 12 ++- .../partition/backups/backups.component.ts | 26 +++++-- .../views/service/backup/backup.component.ts | 24 ++++-- .../views/service/base/base.component.html | 4 +- .../scale-service.component.html | 13 +--- .../scale-service/scale-service.component.ts | 44 +++++++---- 87 files changed, 1133 insertions(+), 354 deletions(-) create mode 100644 src/SfxWeb/cypress/fixtures/app-page/app-arm-managed.json create mode 100644 src/SfxWeb/cypress/fixtures/app-page/services-arm-managed.json create mode 100644 src/SfxWeb/cypress/fixtures/service-page/service-arm-managed.json create mode 100644 src/SfxWeb/cypress/fixtures/service-page/service-stateless-arm-managed.json create mode 100644 src/SfxWeb/src/app/modules/action-dialog/DialogBodyComponent.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/action-dialog.module.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.html rename src/SfxWeb/src/app/{shared/component => modules/action-dialog}/action-dialog/action-dialog.component.scss (100%) rename src/SfxWeb/src/app/{shared/component => modules/action-dialog}/action-dialog/action-dialog.component.spec.ts (100%) create mode 100644 src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/dialog-body.directive.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.html create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.scss create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.spec.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.html create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.scss create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.spec.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.ts create mode 100644 src/SfxWeb/src/app/modules/action-dialog/utils.ts create mode 100644 src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.html create mode 100644 src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.scss create mode 100644 src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.spec.ts create mode 100644 src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.ts delete mode 100644 src/SfxWeb/src/app/shared/component/action-dialog/action-dialog.component.html delete mode 100644 src/SfxWeb/src/app/shared/component/action-dialog/action-dialog.component.ts create mode 100644 src/SfxWeb/src/app/shared/component/arm-warning/arm-warning.component.html create mode 100644 src/SfxWeb/src/app/shared/component/arm-warning/arm-warning.component.scss create mode 100644 src/SfxWeb/src/app/shared/component/arm-warning/arm-warning.component.spec.ts create mode 100644 src/SfxWeb/src/app/shared/component/arm-warning/arm-warning.component.ts diff --git a/src/SfxWeb/cypress/e2e/app.cy.js b/src/SfxWeb/cypress/e2e/app.cy.js index 9cd996760..c7d50c35e 100644 --- a/src/SfxWeb/cypress/e2e/app.cy.js +++ b/src/SfxWeb/cypress/e2e/app.cy.js @@ -105,7 +105,16 @@ context('app', () => { }) cy.get('[data-cy=flips]').should('not.exist') - }) + }) + + it('arm managed app', () => { + addRoute('visualObjectsApplicationType', 'app-page/app-arm-managed.json', apiUrl('/Applications/VisualObjectsApplicationType/?*')) + cy.visit(`/#/apptype/${appName}/app/${appName}`) + + cy.get('[data-cy=armWarning]').should('exist'); + cy.get('[data-cy=actions]').should('not.exist'); + + }) }) diff --git a/src/SfxWeb/cypress/e2e/appType.cy.js b/src/SfxWeb/cypress/e2e/appType.cy.js index 915a7c3a7..9b4adb1d7 100644 --- a/src/SfxWeb/cypress/e2e/appType.cy.js +++ b/src/SfxWeb/cypress/e2e/appType.cy.js @@ -13,20 +13,35 @@ context('app type', () => { }) describe("essentials", () => { - it('load essentials', () => { - cy.get('[data-cy=header').within(() => { - cy.contains(`Application Type ${appTypeName}`).click(); - }) - - cy.get('[data-cy=appTypeVersions]').within(() => { - cy.contains(appTypeName) - cy.contains('16.0.0') + it('load essentials', () => { + cy.get('[data-cy=header').within(() => { + cy.contains(`Application Type ${appTypeName}`).click(); }) - cy.get('[data-cy=applicationsList]').within(() => { - cy.contains(appname) - }) - }) + cy.get('[data-cy=armWarning]').should('exist'); + + cy.get('[data-cy=appTypeVersions]').within(() => { + cy.contains(appTypeName) + cy.contains("tr", "16.0.0").within(()=>{ + cy.contains('button', 'Unprovision'); + cy.contains('a', 'View').should('not.exist'); + }); + cy.contains("tr", "18.0.0").within(()=> { + cy.contains('button', 'unprovision').should('not.exist'); + cy.contains('a', 'View'); + }) + }) + + cy.contains('All').click(); + + cy.get('[data-cy=allAppTypeVersions]').within(() => { + cy.contains('17.0.0') + }); + + cy.get('[data-cy=applicationsList]').within(() => { + cy.contains(appname) + }) + }) it('unprovision', () => { cy.intercept('POST', apiUrl('/ApplicationTypes/VisualObjectsApplicationType/$/Unprovision?*'), { @@ -39,8 +54,8 @@ context('app type', () => { cy.contains("Unprovision").click() }).then(() => { cy.get(".action-modal").within(() => { - cy.get('[data-cy=input-dialog]'); - }).type('VisualObjectsApplicationType') + cy.get('[data-cy=input-dialog]').type('VisualObjectsApplicationType'); + }) cy.get('[data-cy=submit]').click(); }) @@ -53,10 +68,14 @@ context('app type', () => { cy.get('@getunprovision.0').then(request1 => { cy.get('@getunprovision.1').then(request2 => { const versionsSeen = [request1.request.body.ApplicationTypeVersion, request2.request.body.ApplicationTypeVersion]; - expect(versionsSeen).to.include.members(["16.0.0", "17.0.0"]) + expect(versionsSeen).to.include.members(["16.0.0", "17.0.0"]); + expect(versionsSeen).to.not.include("18.0.0"); }) - }) + cy.get('[data-cy=applicationsList]').within(() => { + cy.contains(appname) + }) + }) }) }) diff --git a/src/SfxWeb/cypress/e2e/apps.cy.js b/src/SfxWeb/cypress/e2e/apps.cy.js index 95e2a98fb..3a8ee39a7 100644 --- a/src/SfxWeb/cypress/e2e/apps.cy.js +++ b/src/SfxWeb/cypress/e2e/apps.cy.js @@ -18,6 +18,8 @@ context('apps list page', () => { cy.contains('Applications').click(); }) + cy.get('[data-cy=armWarning]').should('exist'); + cy.get('[data-cy=appslist]').within(() => { cy.contains(appName) }) @@ -64,7 +66,7 @@ context('apps list page', () => { cy.url().should('include', `/#/apps/apptypes`) cy.get('[data-cy=active-app-type]').within(() => { - cy.contains("1") + cy.contains("2") }) cy.get('[data-cy=inactive-app-type]').within(() => { diff --git a/src/SfxWeb/cypress/e2e/cluster.cy.js b/src/SfxWeb/cypress/e2e/cluster.cy.js index 38d88456d..1ad7e39be 100644 --- a/src/SfxWeb/cypress/e2e/cluster.cy.js +++ b/src/SfxWeb/cypress/e2e/cluster.cy.js @@ -946,7 +946,7 @@ context('Cluster page', () => { cy.wait(500); cy.get('[data-cy=command]').within(() => { - cy.get('.detail-pane').should('have.css', 'border-left-color', 'rgb(252, 209, 22)') + cy.get('.detail-pane').should('have.class', 'unsafe') cy.get('[data-cy=requiredInput]').should('have.length', 3) cy.get('[data-cy=optionalInput]').should('have.length', 0) cy.get('[data-cy=warning]').should('include.text', 'HealthState, SourceId, HealthProperty') diff --git a/src/SfxWeb/cypress/e2e/service.cy.js b/src/SfxWeb/cypress/e2e/service.cy.js index 922ad3ec0..ccc571f4a 100644 --- a/src/SfxWeb/cypress/e2e/service.cy.js +++ b/src/SfxWeb/cypress/e2e/service.cy.js @@ -45,6 +45,18 @@ context('service', () => { }) }) + it('arm-managed', () => { + + addRoute("services", "app-page/services-arm-managed.json", apiUrl(`/Applications/${appName}/$/GetServices?*`)) + addRoute("serviceInfo", "service-page/service-arm-managed.json", apiUrl(`${routeFormatter(appName, serviceName)}?*`)) + cy.reload(); + cy.wait(waitRequest); + + cy.get('[data-cy=armWarning]').should('exist'); + cy.get('[data-cy=actions]').should('not.exist'); + + }) + it('stateful information', () => { cy.wait(waitRequest); @@ -177,8 +189,20 @@ context('service', () => { }) }) - it('actions', () => { - cy.wait(waitRequest); + it('arm-managed', () => { + + addRoute("services", "app-page/services-arm-managed.json", apiUrl(`/Applications/${appName}/$/GetServices?*`)) + addRoute("serviceInfo", "service-page/service-stateless-arm-managed.json",apiUrl(`${routeFormatter(appName, statelessServiceName)}?*`)) + cy.reload(); + cy.wait(waitRequest); + + cy.get('[data-cy=armWarning]').should('exist'); + cy.get('[data-cy=actions]').should('not.exist'); + + }) + + it('actions', () => { + cy.wait(waitRequest); cy.get('[data-cy=actions]').within(() => { cy.contains("Actions").click(); @@ -198,7 +222,7 @@ context('service', () => { }) cy.get('[formcontrolname=count').clear().type(2); - cy.get('[type=submit').click(); + cy.get('[data-cy=submit]').click(); cy.wait('@updateService') diff --git a/src/SfxWeb/cypress/fixtures/app-page/app-arm-managed.json b/src/SfxWeb/cypress/fixtures/app-page/app-arm-managed.json new file mode 100644 index 000000000..373f78baa --- /dev/null +++ b/src/SfxWeb/cypress/fixtures/app-page/app-arm-managed.json @@ -0,0 +1,18 @@ +{ + "Name": "fabric:/VisualObjectsApplicationType", + "TypeName": "VisualObjectsApplicationType", + "TypeVersion": "16.0.0", + "Status": "Ready", + "Parameters": [], + "HealthState": "Error", + "ApplicationDefinitionKind": "ServiceFabricApplicationDescription", + "ManagedApplicationIdentity": { + "ManagedIdentities": [] + }, + "Id": "VisualObjectsApplicationType", + "ApplicationMetadata": { + "ArmMetadata": { + "ArmResourceId": "\/subscriptions\/13ad2c84-84fa-4798-ad71-e70c07af873f\/resourcegroups\/sfmc\/providers\/Microsoft.ServiceFabric\/managedClusters\/luslevinsfmc10\/applicationTypes\/VotingType\/versions\/1.0.0" + } + } +} \ No newline at end of file diff --git a/src/SfxWeb/cypress/fixtures/app-page/services-arm-managed.json b/src/SfxWeb/cypress/fixtures/app-page/services-arm-managed.json new file mode 100644 index 000000000..4c7729a21 --- /dev/null +++ b/src/SfxWeb/cypress/fixtures/app-page/services-arm-managed.json @@ -0,0 +1,36 @@ +{ + "ContinuationToken": "", + "Items": [ + { + "ServiceKind": "Stateful", + "Name": "fabric:/VisualObjectsApplicationType/VisualObjects.ActorService", + "TypeName": "VisualObjects.ActorServiceType", + "ManifestVersion": "3.0.0", + "HasPersistedState": true, + "HealthState": "Ok", + "ServiceStatus": "Active", + "IsServiceGroup": false, + "Id": "VisualObjectsApplicationType/VisualObjects.ActorService", + "ServiceMetadata": { + "ArmMetadata": { + "ArmResourceId": "\/subscriptions\/13ad2c84-84fa-4798-ad71-e70c07af873f\/resourcegroups\/sfmc\/providers\/Microsoft.ServiceFabric\/managedClusters\/luslevinsfmc10\/applicationTypes\/VotingType\/versions\/1.0.0" + } + } + }, + { + "ServiceKind": "Stateless", + "Name": "fabric:/VisualObjectsApplicationType/VisualObjects.WebService", + "TypeName": "VisualObjects.WebServiceType", + "ManifestVersion": "12.0.0", + "HealthState": "Ok", + "ServiceStatus": "Active", + "IsServiceGroup": false, + "Id": "VisualObjectsApplicationType/VisualObjects.WebService", + "ServiceMetadata": { + "ArmMetadata": { + "ArmResourceId": "\/subscriptions\/13ad2c84-84fa-4798-ad71-e70c07af873f\/resourcegroups\/sfmc\/providers\/Microsoft.ServiceFabric\/managedClusters\/luslevinsfmc10\/applicationTypes\/VotingType\/versions\/1.0.0" + } + } + } + ] +} \ No newline at end of file diff --git a/src/SfxWeb/cypress/fixtures/appType.json b/src/SfxWeb/cypress/fixtures/appType.json index 9f1e50fe0..848946425 100644 --- a/src/SfxWeb/cypress/fixtures/appType.json +++ b/src/SfxWeb/cypress/fixtures/appType.json @@ -21,7 +21,7 @@ } ], "Status": "Available", - "ApplicationTypeDefi,nitionKind": "ServiceFabricApplicationPackage" + "ApplicationTypeDefinitionKind": "ServiceFabricApplicationPackage" }, { "Name": "VisualObjectsApplicationType", @@ -46,5 +46,35 @@ ], "Status": "Available", "ApplicationTypeDefinitionKind": "ServiceFabricApplicationPackage" + }, + { + "Name": "VisualObjectsApplicationType", + "Version": "18.0.0", + "DefaultParameterList": [ + { + "Key": "VisualObjects.ActorService_MinReplicaSetSize", + "Value": "2" + }, + { + "Key": "VisualObjects.ActorService_PartitionCount", + "Value": "10" + }, + { + "Key": "VisualObjects.ActorService_TargetReplicaSetSize", + "Value": "3" + }, + { + "Key": "VisualObjects.WebService_InstanceCount", + "Value": "1" + } + ], + "Status": "Available", + "ApplicationTypeDefinitionKind": "ServiceFabricApplicationPackage", + "ApplicationTypeMetadata": { + "ApplicationTypeProvisionTimestamp": "2023-02-24T21:04:26.636Z", + "ArmMetadata": { + "ArmResourceId": "\/subscriptions\/13ad2c84-84fa-4798-ad71-e70c07af873f\/resourcegroups\/sfmc\/providers\/Microsoft.ServiceFabric\/managedClusters\/luslevinsfmc10\/applicationTypes\/VotingType\/versions\/1.0.0" + } + } } ] diff --git a/src/SfxWeb/cypress/fixtures/applications.json b/src/SfxWeb/cypress/fixtures/applications.json index 4282ec5d0..7558ff9a2 100644 --- a/src/SfxWeb/cypress/fixtures/applications.json +++ b/src/SfxWeb/cypress/fixtures/applications.json @@ -2,18 +2,37 @@ "ContinuationToken": "", "Items": [ - { - "Name": "fabric:/VisualObjectsApplicationType", - "TypeName": "VisualObjectsApplicationType", - "TypeVersion": "16.0.0", - "Status": "Ready", - "Parameters": [], - "HealthState": "Ok", - "ApplicationDefinitionKind": "ServiceFabricApplicationDescription", - "ManagedApplicationIdentity": { - "ManagedIdentities": [] + { + "Name": "fabric:/VisualObjectsApplicationType", + "TypeName": "VisualObjectsApplicationType", + "TypeVersion": "16.0.0", + "Status": "Ready", + "Parameters": [], + "HealthState": "Ok", + "ApplicationDefinitionKind": "ServiceFabricApplicationDescription", + "ManagedApplicationIdentity": { + "ManagedIdentities": [] + }, + "Id": "VisualObjectsApplicationType" }, - "Id": "VisualObjectsApplicationType" - } + { + "Name": "fabric:/VisualObjectsApplicationType", + "TypeName": "VisualObjectsApplicationType", + "TypeVersion": "18.0.0", + "Status": "Ready", + "Parameters": [], + "HealthState": "Ok", + "ApplicationDefinitionKind": "ServiceFabricApplicationDescription", + "ManagedApplicationIdentity": { + "ManagedIdentities": [] + }, + "Id": "VisualObjectsApplicationType", + "ApplicationMetadata": { + "ArmMetadata": { + "ArmResourceId": "\/subscriptions\/13ad2c84-84fa-4798-ad71-e70c07af873f\/resourcegroups\/sfmc\/providers\/Microsoft.ServiceFabric\/managedClusters\/luslevinsfmc10\/applicationTypes\/VotingType\/versions\/1.0.0" + } + } + } ] + } diff --git a/src/SfxWeb/cypress/fixtures/service-page/service-arm-managed.json b/src/SfxWeb/cypress/fixtures/service-page/service-arm-managed.json new file mode 100644 index 000000000..cafcee334 --- /dev/null +++ b/src/SfxWeb/cypress/fixtures/service-page/service-arm-managed.json @@ -0,0 +1,16 @@ +{ + "ServiceKind": "Stateful", + "Name": "fabric:/VisualObjectsApplicationType/VisualObjects.ActorService", + "TypeName": "VisualObjects.ActorServiceType", + "ManifestVersion": "3.0.0", + "HasPersistedState": true, + "HealthState": "Ok", + "ServiceStatus": "Active", + "IsServiceGroup": false, + "Id": "VisualObjectsApplicationType/VisualObjects.ActorService", + "ServiceMetadata": { + "ArmMetadata": { + "ArmResourceId": "\/subscriptions\/13ad2c84-84fa-4798-ad71-e70c07af873f\/resourcegroups\/sfmc\/providers\/Microsoft.ServiceFabric\/managedClusters\/luslevinsfmc10\/applicationTypes\/VotingType\/versions\/1.0.0" + } + } +} \ No newline at end of file diff --git a/src/SfxWeb/cypress/fixtures/service-page/service-stateless-arm-managed.json b/src/SfxWeb/cypress/fixtures/service-page/service-stateless-arm-managed.json new file mode 100644 index 000000000..681709f84 --- /dev/null +++ b/src/SfxWeb/cypress/fixtures/service-page/service-stateless-arm-managed.json @@ -0,0 +1,15 @@ +{ + "ServiceKind": "Stateless", + "Name": "fabric:/VisualObjectsApplicationType/VisualObjects.WebService", + "TypeName": "VisualObjects.WebServiceType", + "ManifestVersion": "12.0.0", + "HealthState": "Ok", + "ServiceStatus": "Active", + "IsServiceGroup": false, + "Id": "VisualObjectsApplicationType/VisualObjects.WebService", + "ServiceMetadata": { + "ArmMetadata": { + "ArmResourceId": "\/subscriptions\/13ad2c84-84fa-4798-ad71-e70c07af873f\/resourcegroups\/sfmc\/providers\/Microsoft.ServiceFabric\/managedClusters\/luslevinsfmc10\/applicationTypes\/VotingType\/versions\/1.0.0" + } + } +} \ No newline at end of file diff --git a/src/SfxWeb/src/Styles/_modal.scss b/src/SfxWeb/src/Styles/_modal.scss index 38a9de15c..494930f88 100644 --- a/src/SfxWeb/src/Styles/_modal.scss +++ b/src/SfxWeb/src/Styles/_modal.scss @@ -26,6 +26,12 @@ $dialog-input-bg-color: #242424; padding: 18px 15px 18px 15px; border: none !important; + &.warning { + background-color: var(--badge-warning) !important; + color: var(--primary-background-color) !important; + + } + .modal-title { font-weight: var(--font-weight-normal); font-size: 18pt; diff --git a/src/SfxWeb/src/Styles/_vars.scss b/src/SfxWeb/src/Styles/_vars.scss index e21fc4d30..16b055d03 100644 --- a/src/SfxWeb/src/Styles/_vars.scss +++ b/src/SfxWeb/src/Styles/_vars.scss @@ -23,8 +23,12 @@ $detail-pane-pre-color: #d1d1d1 !default; --badge-ok: #7FBA00; --badge-dark-green: green; --badge-warning: #FCD116; + --badge-warning: #F19D03; --badge-error: #E81123; + --badge-error-text: #FF5349; --badge-unknown: #3C3C3C; + + --dark-green: #2E8217; //fonts --font-size-default: 10pt; --font-size-small: 8pt; diff --git a/src/SfxWeb/src/app/Models/Action.ts b/src/SfxWeb/src/app/Models/Action.ts index 468495203..dcbc53588 100644 --- a/src/SfxWeb/src/app/Models/Action.ts +++ b/src/SfxWeb/src/app/Models/Action.ts @@ -1,9 +1,11 @@ import { Observable, of } from 'rxjs'; import { mergeMap, finalize } from 'rxjs/operators'; import { MatDialog } from '@angular/material/dialog'; -import { ActionDialogComponent } from '../shared/component/action-dialog/action-dialog.component'; +import { ActionDialogComponent } from '../modules/action-dialog/action-dialog/action-dialog.component'; import { ComponentType } from '@angular/cdk/portal'; -import { ModalData } from '../ViewModels/Modal'; +import { IModalBody, IModalData, IModalTitle } from '../ViewModels/Modal'; +import { Type } from '@angular/core'; +import { DialogBodyComponent } from '../modules/action-dialog/DialogBodyComponent'; // ----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. @@ -90,7 +92,7 @@ export class ActionWithDialog extends Action { } } -export class ActionWithConfirmationDialog extends ActionWithDialog implements ModalData{ +export class ActionWithConfirmationDialog extends ActionWithDialog implements IModalData { constructor( public dialog: MatDialog, public name: string, @@ -98,15 +100,14 @@ export class ActionWithConfirmationDialog extends ActionWithDialog implements Mo public runningTitle: string, public execute: (...params: any[]) => Observable, public canRun: () => boolean, - public modalTitle?: string, - public modalMessage?: string, - public confirmationKeyword?: string) { - + public modalTitle : IModalTitle, + public modalBody?: IModalBody, + ) { super(dialog, name, title, runningTitle, execute, canRun); } } -export class IsolatedAction extends Action { +export class IsolatedAction extends Action implements IModalData{ constructor( public dialog: MatDialog, public name: string, @@ -115,7 +116,10 @@ export class IsolatedAction extends Action { public data: any, public template: ComponentType, public canRun: () => boolean, - public beforeOpen?: () => Observable) { + public beforeOpen?: () => Observable, + public modalTitle?: IModalTitle, + public modalBody?: IModalBody, + ) { super(name, title, runningTitle, null, canRun); } diff --git a/src/SfxWeb/src/app/Models/DataModels/Application.ts b/src/SfxWeb/src/app/Models/DataModels/Application.ts index 05f7d0abc..6bec196bb 100644 --- a/src/SfxWeb/src/app/Models/DataModels/Application.ts +++ b/src/SfxWeb/src/app/Models/DataModels/Application.ts @@ -1,5 +1,7 @@ -import { IRawApplication, IRawApplicationHealth, IRawApplicationManifest, IRawDeployedApplicationHealthState, - IRawApplicationUpgradeProgress, IRawApplicationBackupConfigurationInfo, IRawUpgradeDomainProgress } from '../RawDataTypes'; +import { + IRawApplication, IRawApplicationHealth, IRawApplicationManifest, IRawDeployedApplicationHealthState, + IRawApplicationUpgradeProgress, IRawApplicationBackupConfigurationInfo +} from '../RawDataTypes'; import { DataModelBase, IDecorators } from './Base'; import { HtmlUtils } from 'src/app/Utils/HtmlUtils'; import { ServiceTypeCollection, ApplicationBackupConfigurationInfoCollection } from './collections/Collections'; @@ -76,6 +78,14 @@ export class Application extends DataModelBase { return RoutesService.getAppTypeViewPath(this.raw.TypeName); } + public get resourceId(): string { + return this.raw.ApplicationMetadata?.ArmMetadata?.ArmResourceId; + } + + public get isArmManaged(): boolean{ + return this.resourceId?.length > 0; + } + public delete(): Observable { const compose = this.raw.ApplicationDefinitionKind === Constants.ComposeApplicationDefinitionKind; const action = compose ? this.data.restClient.deleteComposeApplication(this.id) : this.data.restClient.deleteApplication(this.id); @@ -110,6 +120,11 @@ export class Application extends DataModelBase { if (this.raw.TypeName === Constants.SystemAppTypeName) { return; } + + if (this.isArmManaged) { + return; + } + this.actions.add(new ActionWithConfirmationDialog( this.data.dialog, 'deleteApplication', @@ -117,9 +132,16 @@ export class Application extends DataModelBase { 'Deleting...', () => this.delete(), () => true, - 'Confirm Application Deletion', - `Delete application ${this.name} from cluster ${window.location.host}?`, - this.name)); + { + title: 'Confirm Application Deletion', + }, + { + inputs: { + message: `Delete application ${this.name} from cluster ${window.location.host}?`, + confirmationKeyword: this.name, + } + } + )); } private setAdvancedActions(): void { diff --git a/src/SfxWeb/src/app/Models/DataModels/ApplicationType.ts b/src/SfxWeb/src/app/Models/DataModels/ApplicationType.ts index 2535c227f..75f506752 100644 --- a/src/SfxWeb/src/app/Models/DataModels/ApplicationType.ts +++ b/src/SfxWeb/src/app/Models/DataModels/ApplicationType.ts @@ -2,9 +2,9 @@ import { DataModelBase } from './Base'; import { IRawApplicationType } from '../RawDataTypes'; import { ServiceTypeCollection, ApplicationCollection } from './collections/Collections'; import { DataService } from 'src/app/services/data.service'; -import { HealthStateConstants, Constants } from 'src/app/Common/Constants'; +import { HealthStateConstants } from 'src/app/Common/Constants'; import { CollectionUtils } from 'src/app/Utils/CollectionUtils'; -import { Observable, forkJoin} from 'rxjs'; +import { Observable, forkJoin } from 'rxjs'; import { Application } from './Application'; import { ITextAndBadge, ValueResolver } from 'src/app/Utils/ValueResolver'; import { IResponseMessageHandler } from 'src/app/Common/ResponseMessageHandlers'; @@ -13,6 +13,9 @@ import { Utils } from 'src/app/Utils/Utils'; import { ActionWithConfirmationDialog, IsolatedAction } from '../Action'; import { CreateApplicationComponent } from 'src/app/views/application-type/create-application/create-application.component'; import { RoutesService } from 'src/app/services/routes.service'; +import { MessageWithWarningComponent } from 'src/app/modules/action-dialog/message-wth-warning/message-with-warning.component'; +import { MessageWithConfirmationComponent } from 'src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component'; +import { ActionDialogComponent } from 'src/app/modules/action-dialog/action-dialog/action-dialog.component'; // ----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. @@ -41,6 +44,14 @@ export class ApplicationType extends DataModelBase { return RoutesService.getAppTypeViewPath(this.name); } + public get resourceId(): string { + return this.raw.ApplicationTypeMetadata?.ArmMetadata?.ArmResourceId; + } + + public get isArmManaged(): boolean{ + return this.resourceId?.length > 0; + } + public unprovision(): Observable { return this.data.restClient.unprovisionApplicationType(this.name, this.raw.Version); } @@ -51,6 +62,10 @@ export class ApplicationType extends DataModelBase { private setUpActions() { + if (this.isArmManaged) { + return; + } + this.actions.add(new ActionWithConfirmationDialog( this.data.dialog, 'unprovisionAppType', @@ -58,9 +73,15 @@ export class ApplicationType extends DataModelBase { 'Unprovisioning', () => this.unprovision(), () => true, - 'Confirm Type Unprovision', - `Unprovision application type ${this.name}@${this.raw.Version} from cluster ${window.location.host}?`, - `${this.name}@${this.raw.Version}` + { + title: 'Confirm Type Unprovision', + }, + { + inputs: { + message: `Unprovision application type ${this.name}@${this.raw.Version} from cluster ${window.location.host}?`, + confirmationKeyword: `${this.name}@${this.raw.Version}`, + } + } )); this.actions.add(new IsolatedAction( @@ -71,8 +92,18 @@ export class ApplicationType extends DataModelBase { { appType: this, }, - CreateApplicationComponent, - () => true) + ActionDialogComponent, + () => true, + null, + { + title: "Create app instance", + }, + { + template: CreateApplicationComponent, + inputs: { + appType: this, + } + }) ); } } @@ -99,6 +130,10 @@ export class ApplicationTypeGroup extends DataModelBase { return RoutesService.getAppTypeViewPath(this.name); } + public get isArmManaged(): boolean{ + return this.appTypes.some(app => app.isArmManaged); + } + // Whenever the data.apps get refreshed, it will call this method to // update all applications for all application type group to keep the // applications in sync. @@ -143,9 +178,19 @@ export class ApplicationTypeGroup extends DataModelBase { 'Unprovisioning', () => this.unprovision(), () => true, - 'Confirm Type Unprovision', - `Unprovision all versions of application type ${this.name} from cluster ${window.location.host}?`, - this.name + { + title: 'Confirm Type Unprovision', + class: this.isArmManaged ? 'warning' : null + }, + { + template: this.isArmManaged ? MessageWithWarningComponent : null, + inputs: { + message: `Unprovision all ${this.isArmManaged ? " non-arm managed " : null} versions of application type ${this.name} from cluster ${window.location.host}?`, + confirmationKeyword: this.name, + description: `Some versions of application type ${this.name} are ARM managed, this action will not unprovision those versions.`, + template: MessageWithConfirmationComponent + } + } )); } @@ -153,7 +198,9 @@ export class ApplicationTypeGroup extends DataModelBase { return this.data.getAppTypeGroup(this.name, true).pipe(mergeMap(appTypeGroup => { const unprovisonPromises = []; appTypeGroup.appTypes.forEach(applicationType => { - unprovisonPromises.push(applicationType.unprovision()); + if (!applicationType.isArmManaged) { + unprovisonPromises.push(applicationType.unprovision()); + } }); return forkJoin(unprovisonPromises); })); diff --git a/src/SfxWeb/src/app/Models/DataModels/DeployedCodePackage.ts b/src/SfxWeb/src/app/Models/DataModels/DeployedCodePackage.ts index 3145370e9..329d9680f 100644 --- a/src/SfxWeb/src/app/Models/DataModels/DeployedCodePackage.ts +++ b/src/SfxWeb/src/app/Models/DataModels/DeployedCodePackage.ts @@ -69,9 +69,15 @@ export class DeployedCodePackage extends DataModelBase 'Restarting', () => this.restart(), () => true, - 'Confirm Code Package Restart', - `Restart code package ${this.name}?`, - this.name + { + title: 'Confirm Code Package Restart' + }, + { + inputs: { + message: `Restart code package ${this.name}?`, + confirmationKeyword: this.name + } + } )); } } diff --git a/src/SfxWeb/src/app/Models/DataModels/DeployedReplica.ts b/src/SfxWeb/src/app/Models/DataModels/DeployedReplica.ts index 741cd8c15..ef95748bb 100644 --- a/src/SfxWeb/src/app/Models/DataModels/DeployedReplica.ts +++ b/src/SfxWeb/src/app/Models/DataModels/DeployedReplica.ts @@ -126,9 +126,15 @@ export class DeployedReplica extends DataModelBase { 'Restarting', () => this.restartReplica(), () => true, - `Confirm Replica Restart`, - `Restart Replica for ${serviceName}`, - 'confirm' + { + title: `Confirm Replica Restart` + }, + { + inputs: { + message: `Restart Replica for ${serviceName}`, + confirmationKeyword: 'confirm' + } + } )); } } diff --git a/src/SfxWeb/src/app/Models/DataModels/Node.ts b/src/SfxWeb/src/app/Models/DataModels/Node.ts index 0b456e7f4..4238d1044 100644 --- a/src/SfxWeb/src/app/Models/DataModels/Node.ts +++ b/src/SfxWeb/src/app/Models/DataModels/Node.ts @@ -121,9 +121,15 @@ export class Node extends DataModelBase { 'Removing', () => this.removeNodeState(), () => this.raw.NodeStatus === NodeStatusConstants.Down, - 'Confirm Node Removal', - `Data about node ${this.name} will be completely erased from the cluster ${window.location.host}. All data stored on the node will be lost permanently. Are you sure to continue?`, - this.name + { + title: 'Confirm Node Removal' + }, + { + inputs: { + message: `Data about node ${this.name} will be completely erased from the cluster ${window.location.host}. All data stored on the node will be lost permanently. Are you sure to continue?`, + confirmationKeyword: this.name + } + } ); removeNodeState.isAdvanced = true; this.actions.add(removeNodeState); @@ -137,9 +143,15 @@ export class Node extends DataModelBase { // There are various levels of "disabling" and it should be possible to disable "at a higher level" an already-disabled node. () => this.raw.NodeStatus !== NodeStatusConstants.Down, // We do not track the level of disabling, so we just enable the command as long as the node is not down. - 'Confirm Node Deactivation', - `Deactivate node '${this.name}' from cluster '${window.location.host}'? This node will not become operational again until it is manually reactivated. WARNING: Deactivating nodes can cause data loss if not used with caution. For more information see: https://go.microsoft.com/fwlink/?linkid=825861`, - this.name + { + title: 'Confirm Node Deactivation' + }, + { + inputs: { + message: `Deactivate node '${this.name}' from cluster '${window.location.host}'? This node will not become operational again until it is manually reactivated. WARNING: Deactivating nodes can cause data loss if not used with caution. For more information see: https://go.microsoft.com/fwlink/?linkid=825861`, + confirmationKeyword: this.name + } + } ); deactivePauseNode.isAdvanced = true; this.actions.add(deactivePauseNode); @@ -151,9 +163,15 @@ export class Node extends DataModelBase { 'Deactivating', () => this.deactivate(2), () => this.raw.NodeStatus !== NodeStatusConstants.Down, - 'Confirm Node Deactivation', - `Deactivate node '${this.name}' from cluster '${window.location.host}'? This node will not become operational again until it is manually reactivated. WARNING: Deactivating nodes can cause data loss if not used with caution. For more information see: https://go.microsoft.com/fwlink/?linkid=825861`, - this.name + { + title: 'Confirm Node Deactivation' + }, + { + inputs: { + message: `Deactivate node '${this.name}' from cluster '${window.location.host}'? This node will not become operational again until it is manually reactivated. WARNING: Deactivating nodes can cause data loss if not used with caution. For more information see: https://go.microsoft.com/fwlink/?linkid=825861`, + confirmationKeyword: this.name + } + } ); deactiveRestartNode.isAdvanced = true; this.actions.add(deactiveRestartNode); @@ -165,9 +183,15 @@ export class Node extends DataModelBase { 'Deactivating', () => this.deactivate(3), () => this.raw.NodeStatus !== NodeStatusConstants.Down, - 'Confirm Node Deactivation', - `Deactivate node '${this.name}' from cluster '${window.location.host}'? This node will not become operational again until it is manually reactivated. WARNING: Deactivating nodes can cause data loss if not used with caution. For more information see: https://go.microsoft.com/fwlink/?linkid=825861`, - this.name + { + title: 'Confirm Node Deactivation' + }, + { + inputs: { + message: `Deactivate node '${this.name}' from cluster '${window.location.host}'? This node will not become operational again until it is manually reactivated. WARNING: Deactivating nodes can cause data loss if not used with caution. For more information see: https://go.microsoft.com/fwlink/?linkid=825861`, + confirmationKeyword: this.name + } + } ); deactiveRemoveNodeData.isAdvanced = true; this.actions.add(deactiveRemoveNodeData); @@ -188,10 +212,16 @@ export class Node extends DataModelBase { 'Restarting', () => this.restart(), () => true, - 'Confirm Node Restart', - `Restart node ${this.name} from the cluster ${window.location.host}?`, - // `Restart node ${this.name} from the cluster ${this.data.$location.host()}?`, TODO - this.name + { + title: 'Confirm Node Restart' + }, + { + inputs: { + message: `Restart node ${this.name} from the cluster ${window.location.host}?`, + // `Restart node ${this.name} from the cluster ${this.data.$location.host()}?`, TODO + confirmationKeyword: this.name + } + } )); } diff --git a/src/SfxWeb/src/app/Models/DataModels/Replica.ts b/src/SfxWeb/src/app/Models/DataModels/Replica.ts index 085efaa17..0a763104d 100644 --- a/src/SfxWeb/src/app/Models/DataModels/Replica.ts +++ b/src/SfxWeb/src/app/Models/DataModels/Replica.ts @@ -117,9 +117,15 @@ export class ReplicaOnPartition extends DataModelBase { 'Restarting', () => this.restartReplica(), () => true, - `Confirm Replica Restart`, - `Restart Replica for ${serviceName}`, - 'confirm' + { + title: `Confirm Replica Restart` + }, + { + inputs: { + message: `Restart Replica for ${serviceName}`, + confirmationKeyword: 'confirm' + } + } )); } else if (this.isStatelessService) { this.actions.add(new ActionWithConfirmationDialog( @@ -129,9 +135,15 @@ export class ReplicaOnPartition extends DataModelBase { 'Deleting', () => this.deleteInstance(), () => true, - `Confirm Instance Delete`, - `Delete Instance for ${serviceName}`, - 'confirm' + { + title: `Confirm Instance Delete` + }, + { + inputs: { + message: `Delete Instance for ${serviceName}`, + confirmationKeyword: 'confirm' + } + } )); } diff --git a/src/SfxWeb/src/app/Models/DataModels/Service.ts b/src/SfxWeb/src/app/Models/DataModels/Service.ts index b773cc1fc..0a14ee1d4 100644 --- a/src/SfxWeb/src/app/Models/DataModels/Service.ts +++ b/src/SfxWeb/src/app/Models/DataModels/Service.ts @@ -1,6 +1,8 @@ import { DataModelBase, IDecorators } from './Base'; -import { IRawService, IRawUpdateServiceDescription, IRawServiceHealth, IRawServiceDescription, IRawServiceType, IRawServiceManifest, - IRawCreateServiceDescription, IRawServiceBackupConfigurationInfo, IRawCreateServiceFromTemplateDescription } from '../RawDataTypes'; +import { + IRawService, IRawUpdateServiceDescription, IRawServiceHealth, IRawServiceDescription, IRawServiceType, IRawServiceManifest, + IRawCreateServiceDescription, IRawServiceBackupConfigurationInfo, IRawCreateServiceFromTemplateDescription +} from '../RawDataTypes'; import { PartitionCollection, ServiceBackupConfigurationInfoCollection } from './collections/Collections'; import { DataService } from 'src/app/services/data.service'; import { HealthStateFilterFlags, IClusterHealthChunkQueryDescription, IServiceHealthStateFilter } from '../HealthChunkRawDataTypes'; @@ -19,6 +21,7 @@ import { ActionWithConfirmationDialog, IsolatedAction } from '../Action'; import { ScaleServiceComponent } from 'src/app/views/service/scale-service/scale-service.component'; import { ViewBackupComponent } from 'src/app/modules/backup-restore/view-backup/view-backup.component'; import { RoutesService } from 'src/app/services/routes.service'; +import { ActionDialogComponent } from 'src/app/modules/action-dialog/action-dialog/action-dialog.component'; // ----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License file under the project root for license information. @@ -70,6 +73,14 @@ export class Service extends DataModelBase { return RoutesService.getServiceViewPath(this.parent.raw.TypeName, this.parent.id, this.id); } + public get resourceId(): string { + return this.raw.ServiceMetadata?.ArmMetadata?.ArmResourceId; + } + + public get isArmManaged(): boolean { + return this.resourceId?.length > 0; + } + public addHealthStateFiltersForChildren(clusterHealthChunkQueryDescription: IClusterHealthChunkQueryDescription): IServiceHealthStateFilter { const appFilter = this.parent.addHealthStateFiltersForChildren(clusterHealthChunkQueryDescription); let serviceFilter = appFilter.ServiceFilters.find(filter => filter.ServiceNameFilter === this.name); @@ -102,32 +113,50 @@ export class Service extends DataModelBase { return; } - this.actions.add(new ActionWithConfirmationDialog( - this.data.dialog, - 'deleteService', - 'Delete Service', - 'Deleting', - () => this.delete().pipe(map( () => { - this.data.routes.navigate(() => this.parent.viewPath); - })), - () => true, - 'Confirm Service Deletion', - `Delete service ${this.name} from cluster ${window.location.host}?`, - this.name - )); - - if (this.isStatelessService) { - this.actions.add(new IsolatedAction( + if (!this.isArmManaged) { + this.actions.add(new ActionWithConfirmationDialog( this.data.dialog, - 'scaleService', - 'Scale Service', - 'Scaling Service', - this, - ScaleServiceComponent, - () => this.isStatelessService + 'deleteService', + 'Delete Service', + 'Deleting', + () => this.delete().pipe(map( () => { + this.data.routes.navigate(() => this.parent.viewPath); + })), + () => true, + { + title: 'Confirm Service Deletion', + }, + { + inputs: { + message: `Delete service ${this.name} from cluster ${window.location.host}?`, + confirmationKeyword: this.name, + } + } )); + if (this.isStatelessService) { + this.actions.add(new IsolatedAction( + this.data.dialog, + 'scaleService', + 'Scale Service', + 'Scaling Service', + this, + ActionDialogComponent, + () => this.isStatelessService, + null, + { + title: "Scale Service", + }, + { + template: ScaleServiceComponent, + inputs: { + service: this + } + } + )); + } } + } private delete(): Observable { diff --git a/src/SfxWeb/src/app/Models/DataModels/collections/Collections.ts b/src/SfxWeb/src/app/Models/DataModels/collections/Collections.ts index c12a6c671..6c6abd780 100644 --- a/src/SfxWeb/src/app/Models/DataModels/collections/Collections.ts +++ b/src/SfxWeb/src/app/Models/DataModels/collections/Collections.ts @@ -50,6 +50,10 @@ export class ApplicationCollection extends DataModelCollectionBase return RoutesService.getAppsViewPath(); } + public get isArmManaged(): boolean { + return this.collection.some(app => app.isArmManaged); + } + public mergeClusterHealthStateChunk(clusterHealthChunk: IClusterHealthChunk): Observable { return this.updateCollectionFromHealthChunkList(clusterHealthChunk.ApplicationHealthStateChunks, item => IdGenerator.app(IdUtils.nameToId(item.ApplicationName))).pipe(mergeMap(() => { this.updateAppsHealthState(); diff --git a/src/SfxWeb/src/app/Models/ListSettings.ts b/src/SfxWeb/src/app/Models/ListSettings.ts index 1243f430a..2008f7ba3 100644 --- a/src/SfxWeb/src/app/Models/ListSettings.ts +++ b/src/SfxWeb/src/app/Models/ListSettings.ts @@ -1,4 +1,3 @@ -import { HtmlUtils } from '../Utils/HtmlUtils'; import { Utils } from '../Utils/Utils'; import { HyperLinkComponent } from '../modules/detail-list-templates/hyper-link/hyper-link.component'; import { CopyTextComponent } from '../modules/detail-list-templates/copy-text/copy-text.component'; @@ -11,6 +10,7 @@ import { ITextAndBadge } from '../Utils/ValueResolver'; import { ShortenComponent } from '../modules/detail-list-templates/shorten/shorten.component'; import { HealthbadgeComponent } from '../modules/detail-list-templates/healthbadge/healthbadge.component'; import { IConcurrentEvents } from './eventstore/rcaEngine'; +import { ArmManagedComponent } from '../modules/detail-list-templates/arm-managed/arm-managed.component'; // ----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. @@ -302,13 +302,25 @@ export class ListColumnSettingForLink extends ListColumnSetting { public constructor( propertyPath: string, displayName: string, - public href: (item: any) => string) { + public href: (item: any) => string, + public linkName?: string, + public isExternal: boolean = false, + ) { super(propertyPath, displayName, { enableFilter: false, }); } } +export class ListColumnSettingForArmManaged extends ListColumnSetting { + template = ArmManagedComponent; + public constructor() { + super('isArmManaged', 'Arm Managed', { + enableFilter: true + }) + } +} + export class ListColumnSettingWithCopyText extends ListColumnSetting { template = CopyTextComponent; public constructor( diff --git a/src/SfxWeb/src/app/Models/RawDataTypes.ts b/src/SfxWeb/src/app/Models/RawDataTypes.ts index d609f09e4..15fa5bd7d 100644 --- a/src/SfxWeb/src/app/Models/RawDataTypes.ts +++ b/src/SfxWeb/src/app/Models/RawDataTypes.ts @@ -22,6 +22,7 @@ export interface IRawApplication { Status: string; HealthState: string; ApplicationDefinitionKind: string; + ApplicationMetadata?: IRawApplicationMetadata } export class IRawBackupEntity{ EntityKind: BackupEntityKind; @@ -131,6 +132,8 @@ export interface IRawApplicationType { Status: string; StatusDetails: string; DefaultParameterList: IRawParameter[]; + ApplicationTypeMetadata?: IRawApplicationTypeMetadata + } export interface IRawUpgradeDomain { @@ -718,6 +721,7 @@ export interface IRawService { HasPersistedState: boolean; // Only shows up when this is a stateful service. HealthState: string; IsServiceGroup: boolean; + ServiceMetadata?: IRawServiceMetadata } export interface IRawServiceCorrelationDescription { @@ -1145,4 +1149,19 @@ export interface IRawApplicationNameInfo{ export interface IRawServiceNameInfo{ Id: string; Name: string; - } +} + +export interface IRawApplicationMetadata{ + ArmMetadata: IRawArmMetadata; +} + +export interface IRawApplicationTypeMetadata{ + ArmMetadata: IRawArmMetadata; +} + +export interface IRawServiceMetadata{ + ArmMetadata: IRawArmMetadata; +} +export interface IRawArmMetadata{ + ArmResourceId?: string; +} diff --git a/src/SfxWeb/src/app/ViewModels/Modal.ts b/src/SfxWeb/src/app/ViewModels/Modal.ts index 20fcc69d3..05829b42c 100644 --- a/src/SfxWeb/src/app/ViewModels/Modal.ts +++ b/src/SfxWeb/src/app/ViewModels/Modal.ts @@ -1,6 +1,18 @@ -export interface ModalData { +import { DialogBodyComponent } from "../modules/action-dialog/DialogBodyComponent"; +import { Type } from "@angular/core" + +export interface IModalTitle { title: string, - modalTitle?: string, - modalMessage?: string, - confirmationKeyword?: string + class?: string +} + +export interface IModalBody { + template?: Type; + inputs: any; +} + +export interface IModalData { + title: string, + modalTitle?: IModalTitle, //TODO: turn into required after reworking isolatedAction + modalBody?: IModalBody } \ No newline at end of file diff --git a/src/SfxWeb/src/app/app.module.ts b/src/SfxWeb/src/app/app.module.ts index f27d75372..106af761a 100644 --- a/src/SfxWeb/src/app/app.module.ts +++ b/src/SfxWeb/src/app/app.module.ts @@ -19,6 +19,7 @@ import { TelemetrySnackBarComponent } from './telemetry-snack-bar/telemetry-snac import { MatSnackBarModule } from '@angular/material/snack-bar'; import { AppInsightsErrorHandler } from './error-handling'; import { StandaloneIntegrationService } from './services/standalone-integration.service'; +import { ActionDialogModule } from './modules/action-dialog/action-dialog.module'; @NgModule({ declarations: [ @@ -37,7 +38,8 @@ import { StandaloneIntegrationService } from './services/standalone-integration. ReactiveFormsModule, NgbTooltipModule, MatSnackBarModule, - DebuggingModule + DebuggingModule, + ActionDialogModule ], providers: [ diff --git a/src/SfxWeb/src/app/modules/action-dialog/DialogBodyComponent.ts b/src/SfxWeb/src/app/modules/action-dialog/DialogBodyComponent.ts new file mode 100644 index 000000000..2fe86906a --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/DialogBodyComponent.ts @@ -0,0 +1,8 @@ +import { EventEmitter } from "@angular/core"; +import { Observable } from "rxjs"; + +export interface DialogBodyComponent { + inputs: any; //reference to the @input() decorator in an angular component + disableSubmit?: EventEmitter; + ok?: () => Observable +} \ No newline at end of file diff --git a/src/SfxWeb/src/app/modules/action-dialog/action-dialog.module.ts b/src/SfxWeb/src/app/modules/action-dialog/action-dialog.module.ts new file mode 100644 index 000000000..4b23a4aae --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/action-dialog.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from 'src/app/shared/shared.module'; +import { FormsModule } from '@angular/forms'; +import { ActionDialogComponent } from './action-dialog/action-dialog.component'; +import { MessageWithConfirmationComponent } from './message-with-confirmation/message-with-confirmation.component'; +import { MessageWithWarningComponent } from './message-wth-warning/message-with-warning.component'; +import { DialogBodyDirective } from './dialog-body.directive'; + +@NgModule({ + declarations: [ActionDialogComponent, MessageWithConfirmationComponent, MessageWithWarningComponent, DialogBodyDirective], + imports: [ + CommonModule, + SharedModule, + FormsModule + ], + exports: [ActionDialogComponent, MessageWithConfirmationComponent, MessageWithWarningComponent, DialogBodyDirective] +}) +export class ActionDialogModule {} diff --git a/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.html b/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.html new file mode 100644 index 000000000..735ef7cd2 --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.html @@ -0,0 +1,12 @@ +
+
+

{{data.modalTitle.title}}

+
+ + +
diff --git a/src/SfxWeb/src/app/shared/component/action-dialog/action-dialog.component.scss b/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.scss similarity index 100% rename from src/SfxWeb/src/app/shared/component/action-dialog/action-dialog.component.scss rename to src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.scss diff --git a/src/SfxWeb/src/app/shared/component/action-dialog/action-dialog.component.spec.ts b/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.spec.ts similarity index 100% rename from src/SfxWeb/src/app/shared/component/action-dialog/action-dialog.component.spec.ts rename to src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.spec.ts diff --git a/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.ts b/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.ts new file mode 100644 index 000000000..b071fed9f --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/action-dialog/action-dialog.component.ts @@ -0,0 +1,52 @@ +import { Component, Inject, ViewChild, AfterViewInit } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { IModalData } from 'src/app/ViewModels/Modal'; +import { MessageWithConfirmationComponent } from '../message-with-confirmation/message-with-confirmation.component'; +import { DialogBodyDirective } from '../dialog-body.directive'; +import { DialogBodyComponent } from '../DialogBodyComponent'; +import { ActionDialogUtils } from '../utils'; + +@Component({ + selector: 'app-action-dialog', + templateUrl: './action-dialog.component.html', + styleUrls: ['./action-dialog.component.scss'] +}) +export class ActionDialogComponent implements AfterViewInit { + + @ViewChild(DialogBodyDirective) body: DialogBodyDirective; + disableSubmit = false; + modalBody: DialogBodyComponent; + + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: IModalData) { } + + ngAfterViewInit() { + if (!this.data.modalBody?.template) { + this.modalBody = ActionDialogUtils.createChildComponent(this.body, this.data.modalBody.inputs, MessageWithConfirmationComponent, (value) => { this.setSumbitDisable(value) }); + } + else { + this.modalBody = ActionDialogUtils.createChildComponent(this.body, this.data.modalBody.inputs, this.data.modalBody.template, (value) => { this.setSumbitDisable(value) }); + } + } + + ok() { + if (this.modalBody?.ok) { + this.modalBody.ok().subscribe((value) => { + if (value) { + this.dialogRef.close(true); + } + }); + } + else { + this.dialogRef.close(true); + } + } + + cancel() { + this.dialogRef.close(false); + } + + setSumbitDisable(value: boolean) { + this.disableSubmit = value; + } +} diff --git a/src/SfxWeb/src/app/modules/action-dialog/dialog-body.directive.ts b/src/SfxWeb/src/app/modules/action-dialog/dialog-body.directive.ts new file mode 100644 index 000000000..255c1b556 --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/dialog-body.directive.ts @@ -0,0 +1,10 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[appDialogBody]' +}) +export class DialogBodyDirective { + + constructor(public viewContainerRef: ViewContainerRef) { } + +} \ No newline at end of file diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.html b/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.html new file mode 100644 index 000000000..a0aa3e686 --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.scss b/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.spec.ts b/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.spec.ts new file mode 100644 index 000000000..0bdaff5ee --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MessageWithConfirmationComponent } from './message-with-confirmation.component'; + +describe('ActionDialogTemplateComponent', () => { + let component: MessageWithConfirmationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MessageWithConfirmationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MessageWithConfirmationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.ts b/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.ts new file mode 100644 index 000000000..6d897036d --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/message-with-confirmation/message-with-confirmation.component.ts @@ -0,0 +1,31 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { DialogBodyComponent } from '../DialogBodyComponent'; + +@Component({ + selector: 'app-message-with-confirmation', + templateUrl: './message-with-confirmation.component.html', + styleUrls: ['./message-with-confirmation.component.scss'] +}) +export class MessageWithConfirmationComponent implements OnInit, DialogBodyComponent { + + @Input() inputs: {message?: string, confirmationKeyword?: string}; + @Output() disableSubmit = new EventEmitter(); + + userInput = ''; + placeHolderText = ''; + + ngOnInit() { + this.placeHolderText = `Type in ${this.inputs.confirmationKeyword} to continue`; + this.userInputChange(this.userInput); + } + + userInputChange(value) { + if (this.inputs.confirmationKeyword && this.inputs.confirmationKeyword !== value.trim()) { + this.disableSubmit.emit(true); + } + else { + this.disableSubmit.emit(false); + } + } + +} diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.html b/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.html new file mode 100644 index 000000000..6b7de77de --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.html @@ -0,0 +1,6 @@ + +
+ \ No newline at end of file diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.scss b/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.spec.ts b/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.spec.ts new file mode 100644 index 000000000..d04734b4f --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MessageWithWarningComponent } from './message-with-warning.component'; + +describe('ArmWarningComponent', () => { + let component: MessageWithWarningComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MessageWithWarningComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MessageWithWarningComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.ts b/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.ts new file mode 100644 index 000000000..589835a54 --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/message-wth-warning/message-with-warning.component.ts @@ -0,0 +1,38 @@ +import { AfterViewInit, Component, EventEmitter, Input, Output, Type, ViewChild } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { DialogBodyDirective } from '../dialog-body.directive'; +import { DialogBodyComponent } from '../DialogBodyComponent'; +import { ActionDialogUtils } from '../utils'; + +@Component({ + selector: 'app-message-with-warning', + templateUrl: './message-with-warning.component.html', + styleUrls: ['./message-with-warning.component.scss'] +}) +export class MessageWithWarningComponent implements AfterViewInit { + + @ViewChild(DialogBodyDirective) body: DialogBodyDirective; + @Input() inputs: {description: string, link: string, linkText: string, template?: Type}; + @Output() disableSubmit = new EventEmitter(); + + instance: DialogBodyComponent; + + ngAfterViewInit(): void { + if (this.inputs.template) { + this.instance = ActionDialogUtils.createChildComponent(this.body, this.inputs, this.inputs.template, (value) => { this.emitEvent(value) }); + } + } + + emitEvent(value) { + this.disableSubmit.emit(value); + } + + ok(): Observable { + if (this.instance?.ok) { + return this.instance.ok(); + } + else { + return of(true); + } + } +} diff --git a/src/SfxWeb/src/app/modules/action-dialog/utils.ts b/src/SfxWeb/src/app/modules/action-dialog/utils.ts new file mode 100644 index 000000000..587b68b7e --- /dev/null +++ b/src/SfxWeb/src/app/modules/action-dialog/utils.ts @@ -0,0 +1,17 @@ +import { Type } from "@angular/core"; +import { DialogBodyDirective } from "./dialog-body.directive"; +import { DialogBodyComponent } from "./DialogBodyComponent"; + +export class ActionDialogUtils { + + public static createChildComponent(parent: DialogBodyDirective, inputs: any, template: Type, disableSubmit?: (boolean)=>void) : DialogBodyComponent { + + var child: DialogBodyComponent = parent.viewContainerRef.createComponent(template).instance; + child.inputs = inputs; + if (child.disableSubmit && disableSubmit) { + child.disableSubmit.subscribe((value) => disableSubmit(value)); + } + + return child; + } +} \ No newline at end of file diff --git a/src/SfxWeb/src/app/modules/charts/dashboard-tile/dashboard-tile.component.ts b/src/SfxWeb/src/app/modules/charts/dashboard-tile/dashboard-tile.component.ts index 6d0f3b1c7..916cedb75 100644 --- a/src/SfxWeb/src/app/modules/charts/dashboard-tile/dashboard-tile.component.ts +++ b/src/SfxWeb/src/app/modules/charts/dashboard-tile/dashboard-tile.component.ts @@ -126,9 +126,9 @@ export class DashboardTileComponent implements OnInit, AfterViewInit, OnChanges getDataSet(): PointOptionsObject[] { const colors = { - Healthy: '#7FBA00', - Warning: '#FCD116', - Error: '#E81123' + Healthy: 'var(--badge-ok)', + Warning: 'var(--badge-warning)', + Error: 'var(--badge-error)' }; const data = this.data.dataPoints.map(p => { diff --git a/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.html b/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.html new file mode 100644 index 000000000..41c6be30a --- /dev/null +++ b/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.html @@ -0,0 +1,4 @@ + +
+ View +
\ No newline at end of file diff --git a/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.scss b/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.spec.ts b/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.spec.ts new file mode 100644 index 000000000..844eb473a --- /dev/null +++ b/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ArmManagedComponent } from './arm-managed.component'; + +describe('ArmManagedComponent', () => { + let component: ArmManagedComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ArmManagedComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ArmManagedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.ts b/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.ts new file mode 100644 index 000000000..5b1e1af1c --- /dev/null +++ b/src/SfxWeb/src/app/modules/detail-list-templates/arm-managed/arm-managed.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { ListColumnSettingForArmManaged } from 'src/app/Models/ListSettings'; +import { DetailBaseComponent } from 'src/app/ViewModels/detail-table-base.component'; + +@Component({ + selector: 'app-arm-managed', + templateUrl: './arm-managed.component.html', + styleUrls: ['./arm-managed.component.scss'] +}) +export class ArmManagedComponent implements OnInit, DetailBaseComponent { + + item: any; + listSetting: ListColumnSettingForArmManaged; + + toolTip: string; + link: string; + + constructor() { } + + ngOnInit(): void { + this.toolTip = this.item.resourceId; + this.link = this.item.resourceId ? `https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource${this.item.resourceId}/overview` : null; + } + +} diff --git a/src/SfxWeb/src/app/modules/detail-list-templates/detail-list-templates.module.ts b/src/SfxWeb/src/app/modules/detail-list-templates/detail-list-templates.module.ts index aa8cbd482..8f804f225 100644 --- a/src/SfxWeb/src/app/modules/detail-list-templates/detail-list-templates.module.ts +++ b/src/SfxWeb/src/app/modules/detail-list-templates/detail-list-templates.module.ts @@ -17,12 +17,13 @@ import { ShortenComponent } from './shorten/shorten.component'; import { CustomTrackByPipe } from './custom-track-by.pipe'; import { HealthbadgeComponent } from './healthbadge/healthbadge.component'; import { FullDescriptionComponent } from './full-description/full-description.component'; +import { ArmManagedComponent } from './arm-managed/arm-managed.component'; @NgModule({ declarations: [HyperLinkComponent, DetailTableResolverComponent, ResolverDirective, CopyTextComponent, ResolverDirective, FullDescriptionComponent, - DetailListComponent, PagerComponent, UtcTimestampComponent, ExportModalComponent, QuestionToolTipComponent, ShortenComponent, CustomTrackByPipe, HealthbadgeComponent], + DetailListComponent, PagerComponent, UtcTimestampComponent, ExportModalComponent, QuestionToolTipComponent, ShortenComponent, CustomTrackByPipe, HealthbadgeComponent, ArmManagedComponent], imports: [ CommonModule, RouterModule, diff --git a/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.html b/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.html index aa2607df3..99a696529 100644 --- a/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.html +++ b/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.html @@ -4,6 +4,7 @@ {{value}} - - {{value}} - + diff --git a/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.ts b/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.ts index d151ad370..ce490615c 100644 --- a/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.ts +++ b/src/SfxWeb/src/app/modules/detail-list-templates/hyper-link/hyper-link.component.ts @@ -18,8 +18,18 @@ export class HyperLinkComponent implements OnInit, DetailBaseComponent { constructor() { } ngOnInit() { - this.value = this.listSetting.getValue(this.item); + if (this.listSetting.linkName) { + this.value = this.listSetting.linkName; + } + else { + this.value = this.listSetting.getValue(this.item); + } + this.link = this.listSetting.href(this.item); + if (this.link === null) { + this.value = ""; + } + } } diff --git a/src/SfxWeb/src/app/modules/event-store/row-display/row-display.component.ts b/src/SfxWeb/src/app/modules/event-store/row-display/row-display.component.ts index 82d61dfbb..4ea32409d 100644 --- a/src/SfxWeb/src/app/modules/event-store/row-display/row-display.component.ts +++ b/src/SfxWeb/src/app/modules/event-store/row-display/row-display.component.ts @@ -22,11 +22,11 @@ export class RowDisplayComponent implements OnInit, DetailBaseComponent { this.value = Utils.result(this.item, this.listSetting.propertyPath); let color = null; if (HtmlUtils.eventTypesUtil.isResolved(this.item.raw)) { - color = '#3AA655'; + color = 'var(--badge-ok)'; } else if (HtmlUtils.eventTypesUtil.isWarning(this.item.raw)) { - color = '#F2C649'; + color = 'var(--badge-warning)'; } else if (HtmlUtils.eventTypesUtil.isError(this.item.raw)) { - color = '#FF5349'; + color = 'var(--badge-error-text)'; } if (color) { this.color = color; diff --git a/src/SfxWeb/src/app/modules/imagestore/folder-actions/folder-actions.component.ts b/src/SfxWeb/src/app/modules/imagestore/folder-actions/folder-actions.component.ts index 0f77391bf..47f374f77 100644 --- a/src/SfxWeb/src/app/modules/imagestore/folder-actions/folder-actions.component.ts +++ b/src/SfxWeb/src/app/modules/imagestore/folder-actions/folder-actions.component.ts @@ -26,9 +26,15 @@ export class FolderActionsComponent implements DetailBaseComponent { '', () => this.listSetting.imagestore.deleteContent(this.item.path), () => true, - 'Confirm Deletion', - `Delete ${this.item.path}? This action cannot be undone`, - this.item.path).run(); + { + title: 'Confirm Deletion' + }, + { + inputs: { + message: `Delete ${this.item.path}? This action cannot be undone`, + confirmationKeyword: this.item.path + } + }).run(); } } diff --git a/src/SfxWeb/src/app/modules/powershell-commands/powershell-commands/powershell-commands.component.ts b/src/SfxWeb/src/app/modules/powershell-commands/powershell-commands/powershell-commands.component.ts index ab2b43a75..683005ed2 100644 --- a/src/SfxWeb/src/app/modules/powershell-commands/powershell-commands/powershell-commands.component.ts +++ b/src/SfxWeb/src/app/modules/powershell-commands/powershell-commands/powershell-commands.component.ts @@ -1,11 +1,10 @@ import { ChangeDetectionStrategy, Component, Input, ViewChild, OnChanges } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { NgbNav, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'; -import { result } from 'cypress/types/lodash'; import { CommandSafetyLevel, PowershellCommand } from 'src/app/Models/PowershellCommand'; import { SettingsService } from 'src/app/services/settings.service'; -import { ActionDialogComponent } from 'src/app/shared/component/action-dialog/action-dialog.component'; -import { ModalData } from 'src/app/ViewModels/Modal'; +import { ActionDialogComponent } from 'src/app/modules/action-dialog/action-dialog/action-dialog.component'; +import { IModalBody, IModalData, IModalTitle } from 'src/app/ViewModels/Modal'; @Component({ selector: 'app-powershell-commands', @@ -13,12 +12,16 @@ import { ModalData } from 'src/app/ViewModels/Modal'; styleUrls: ['./powershell-commands.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class PowershellCommandsComponent implements ModalData, OnChanges{ +export class PowershellCommandsComponent implements IModalData, OnChanges{ title: string = 'Acknowledge'; - modalTitle: string = 'Warning'; - modalMessage: string; + modalTitle: IModalTitle = { + title: 'Warning', + class: 'warning' + } + modalBody: IModalBody = { inputs: {message: ''} }; + activeId:any = 1; safetyLevelEnum = CommandSafetyLevel; @@ -41,12 +44,12 @@ export class PowershellCommandsComponent implements ModalData, OnChanges{ onNavChange(e: NgbNavChangeEvent) { if (e.nextId == 2 && !this.settings.getSessionVariable('unsafeCommandsWarned')) { e.preventDefault(); - this.modalMessage = "The commands you are about to view are potentially unsafe, and executing them can result in undesirable results. Please ensure you understand their risks." + this.modalBody.inputs.message = "The commands you are about to view are potentially unsafe, and executing them can result in undesirable results. Please ensure you understand their risks." this.openWarningModal('unsafeCommandsWarned', e.nextId); } else if (e.nextId == 3 && !this.settings.getSessionVariable('dangerCommandsWarned')) { e.preventDefault(); - this.modalMessage = "The commands you are about to view are potentially very dangerous to the cluster, and executing them incorrectly can lead to dire consequences." + this.modalBody.inputs.message = "The commands you are about to view are potentially very dangerous to the cluster, and executing them incorrectly can lead to dire consequences." this.openWarningModal('dangerCommandsWarned', e.nextId); } diff --git a/src/SfxWeb/src/app/modules/tree/tree-view/tree-view.component.html b/src/SfxWeb/src/app/modules/tree/tree-view/tree-view.component.html index ad0636504..c5ef526b5 100644 --- a/src/SfxWeb/src/app/modules/tree/tree-view/tree-view.component.html +++ b/src/SfxWeb/src/app/modules/tree/tree-view/tree-view.component.html @@ -7,17 +7,17 @@