diff --git a/console/src/app/modules/features/features.component.html b/console/src/app/modules/features/features.component.html index 175d33fac3..fb7e2137ff 100644 --- a/console/src/app/modules/features/features.component.html +++ b/console/src/app/modules/features/features.component.html @@ -265,6 +265,8 @@ +

{{'FEATURES.HEADERS.FLOWS' | translate}}

+
@@ -272,9 +274,25 @@ {{'FEATURES.DATA.FLOWS' | translate}} - - +
+ +
+ + + {{ 'FEATURES.DATA.FLOW.TYPE' | translate }} + + + {{ 'FEATURES.DATA.FLOW.ACTIONSALLOWED.'+allowedType | translate}} + + + + + + {{ 'FEATURES.DATA.FLOW.COUNT' | translate }} + +
diff --git a/console/src/app/modules/features/features.component.scss b/console/src/app/modules/features/features.component.scss index 8bac21fb8b..1007090ac7 100644 --- a/console/src/app/modules/features/features.component.scss +++ b/console/src/app/modules/features/features.component.scss @@ -17,10 +17,10 @@ .title { font-size: 14px; color: var(--grey); - margin-bottom: .5rem; + margin-bottom: 0.5rem; a { - margin-left: .5rem; + margin-left: 0.5rem; cursor: pointer; } } @@ -30,19 +30,19 @@ align-items: center; a { - margin-left: .5rem; + margin-left: 0.5rem; } } img { height: 15px; width: auto; - margin-left: .5rem; + margin-left: 0.5rem; } } .spinner { - margin: .5rem; + margin: 0.5rem; } .error { @@ -60,8 +60,8 @@ height: 1px; width: 100%; background-color: var(--grey); - opacity: .5; - margin: .5rem 0; + opacity: 0.5; + margin: 0.5rem 0; display: block; } @@ -80,7 +80,7 @@ .row { display: flex; align-items: center; - padding: .3rem 0; + padding: 0.3rem 0; .featureavatar { margin-right: 1rem; @@ -137,7 +137,7 @@ } .left-desc { - font-size: .9rem; + font-size: 0.9rem; margin-right: 1rem; } @@ -149,6 +149,18 @@ display: flex; align-items: center; } + + .flow-select { + flex-shrink: 1; + min-width: 150px; + margin-left: 1rem; + } + + .flow-count { + flex-shrink: 1; + width: 70px; + margin-left: 1rem; + } } } diff --git a/console/src/app/modules/features/features.component.ts b/console/src/app/modules/features/features.component.ts index febe8cdf4c..1d5fceeece 100644 --- a/console/src/app/modules/features/features.component.ts +++ b/console/src/app/modules/features/features.component.ts @@ -8,7 +8,7 @@ import { SetDefaultFeaturesRequest, SetOrgFeaturesRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Features } from 'src/app/proto/generated/zitadel/features_pb'; +import { ActionsAllowed, Features } from 'src/app/proto/generated/zitadel/features_pb'; import { GetFeaturesResponse } from 'src/app/proto/generated/zitadel/management_pb'; import { Org } from 'src/app/proto/generated/zitadel/org_pb'; import { AdminService } from 'src/app/services/admin.service'; @@ -46,6 +46,13 @@ export class FeaturesComponent implements OnDestroy { public stripeURL: string = ''; public stripeCustomer!: StripeCustomer; + public actionsSelection: any = [ + ActionsAllowed.ACTIONS_ALLOWED_NOT_ALLOWED, + ActionsAllowed.ACTIONS_ALLOWED_MAX, + ActionsAllowed.ACTIONS_ALLOWED_UNLIMITED, + ]; + public ActionsAllowed: any = ActionsAllowed; + constructor( private route: ActivatedRoute, private toast: ToastService, @@ -60,27 +67,32 @@ export class FeaturesComponent implements OnDestroy { if (temporg) { this.org = temporg; } - this.sub = this.route.data.pipe(switchMap(data => { - this.serviceType = data.serviceType; - if (this.serviceType === FeatureServiceType.MGMT) { - this.managementService = this.injector.get(ManagementService as Type); - } - return this.route.params; - })).subscribe(_ => { - this.fetchData(); - }); + this.sub = this.route.data + .pipe( + switchMap((data) => { + this.serviceType = data.serviceType; + if (this.serviceType === FeatureServiceType.MGMT) { + this.managementService = this.injector.get(ManagementService as Type); + } + return this.route.params; + }), + ) + .subscribe((_) => { + this.fetchData(); + }); if (this.serviceType === FeatureServiceType.MGMT) { this.customerLoading = true; - this.subService.getCustomer(this.org.id) - .then(payload => { + this.subService + .getCustomer(this.org.id) + .then((payload) => { this.customerLoading = false; this.stripeCustomer = payload; if (this.customerValid) { this.getLinkToStripe(); } }) - .catch(error => { + .catch((error) => { this.customerLoading = false; console.error(error); }); @@ -99,13 +111,16 @@ export class FeaturesComponent implements OnDestroy { width: '400px', }); - dialogRefPhone.afterClosed().subscribe(customer => { + dialogRefPhone.afterClosed().subscribe((customer) => { if (customer) { console.log(customer); this.stripeCustomer = customer; - this.subService.setCustomer(this.org.id, customer).then(() => { - this.getLinkToStripe(); - }).catch(console.error); + this.subService + .setCustomer(this.org.id, customer) + .then(() => { + this.getLinkToStripe(); + }) + .catch(console.error); } }); } @@ -113,12 +128,13 @@ export class FeaturesComponent implements OnDestroy { public getLinkToStripe(): void { if (this.serviceType === FeatureServiceType.MGMT) { this.stripeLoading = true; - this.subService.getLink(this.org.id, window.location.href) - .then(payload => { + this.subService + .getLink(this.org.id, window.location.href) + .then((payload) => { this.stripeLoading = false; this.stripeURL = payload.redirect_url; }) - .catch(error => { + .catch((error) => { this.stripeLoading = false; console.error(error); }); @@ -126,7 +142,7 @@ export class FeaturesComponent implements OnDestroy { } public fetchData(): void { - this.getData().then(resp => { + this.getData().then((resp) => { if (resp?.features) { this.features = resp.features; } @@ -167,13 +183,18 @@ export class FeaturesComponent implements OnDestroy { req.setPrivacyPolicy(this.features.privacyPolicy); req.setMetadataUser(this.features.metadataUser); req.setLockoutPolicy(this.features.lockoutPolicy); - req.setActions(this.features.actions); + // req.setActions(this.features.actions); + req.setActionsAllowed(this.features.actionsAllowed); + req.setMaxActions(this.features.maxActions); - this.adminService.setOrgFeatures(req).then(() => { - this.toast.showInfo('POLICY.TOAST.SET', true); - }).catch(error => { - this.toast.showError(error); - }); + this.adminService + .setOrgFeatures(req) + .then(() => { + this.toast.showInfo('POLICY.TOAST.SET', true); + }) + .catch((error) => { + this.toast.showError(error); + }); break; case FeatureServiceType.ADMIN: // update Default org iam policy? @@ -192,27 +213,35 @@ export class FeaturesComponent implements OnDestroy { dreq.setCustomTextMessage(this.features.customTextMessage); dreq.setMetadataUser(this.features.metadataUser); dreq.setLockoutPolicy(this.features.lockoutPolicy); - dreq.setActions(this.features.actions); + // dreq.setActions(this.features.actions); + dreq.setActionsAllowed(this.features.actionsAllowed); + dreq.setMaxActions(this.features.maxActions); - this.adminService.setDefaultFeatures(dreq).then(() => { - this.toast.showInfo('POLICY.TOAST.SET', true); - }).catch(error => { - this.toast.showError(error); - }); + this.adminService + .setDefaultFeatures(dreq) + .then(() => { + this.toast.showInfo('POLICY.TOAST.SET', true); + }) + .catch((error) => { + this.toast.showError(error); + }); break; } } public resetFeatures(): void { if (this.serviceType === FeatureServiceType.MGMT) { - this.adminService.resetOrgFeatures(this.org.id).then(() => { - this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true); - setTimeout(() => { - this.fetchData(); - }, 1000); - }).catch(error => { - this.toast.showError(error); - }); + this.adminService + .resetOrgFeatures(this.org.id) + .then(() => { + this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true); + setTimeout(() => { + this.fetchData(); + }, 1000); + }) + .catch((error) => { + this.toast.showError(error); + }); } } @@ -225,13 +254,15 @@ export class FeaturesComponent implements OnDestroy { } get customerValid(): boolean { - return !!this.stripeCustomer?.contact && + return ( + !!this.stripeCustomer?.contact && !!this.stripeCustomer?.address && !!this.stripeCustomer?.city && - !!this.stripeCustomer?.postal_code; + !!this.stripeCustomer?.postal_code + ); } get customerCountry(): Country | undefined { - return COUNTRIES.find(country => country.isoCode === this.stripeCustomer.country); + return COUNTRIES.find((country) => country.isoCode === this.stripeCustomer.country); } } diff --git a/console/src/app/pages/actions/action-table/action-table.component.html b/console/src/app/pages/actions/action-table/action-table.component.html index 4e5cc3a046..dde77d7c3f 100644 --- a/console/src/app/pages/actions/action-table/action-table.component.html +++ b/console/src/app/pages/actions/action-table/action-table.component.html @@ -1,10 +1,19 @@ -
+
add{{ 'ACTIONS.NEW' | translate }}
+
+ + +
+
diff --git a/console/src/app/pages/actions/action-table/action-table.component.scss b/console/src/app/pages/actions/action-table/action-table.component.scss index 818d7f1c87..4d938877a6 100644 --- a/console/src/app/pages/actions/action-table/action-table.component.scss +++ b/console/src/app/pages/actions/action-table/action-table.component.scss @@ -1,3 +1,6 @@ +.action-state-btn { + margin-left: 0.5rem; +} .table-wrapper { overflow: auto; diff --git a/console/src/app/pages/actions/action-table/action-table.component.ts b/console/src/app/pages/actions/action-table/action-table.component.ts index 65a2e92990..7ed6f9f994 100644 --- a/console/src/app/pages/actions/action-table/action-table.component.ts +++ b/console/src/app/pages/actions/action-table/action-table.component.ts @@ -137,4 +137,40 @@ export class ActionTableComponent implements OnInit { public refreshPage(): void { this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize); } + + public deactivateSelection(): Promise { + const prom = this.selection.selected.map((action) => { + return this.mgmtService.deactivateAction(action.id); + }); + + return Promise.all(prom) + .then(() => { + this.selection.clear(); + this.toast.showInfo('FLOWS.TOAST.ACTIONDEACTIVATED', true); + this.getData(10, 0); + }) + .catch((error) => { + this.selection.clear(); + this.toast.showError(error); + this.getData(10, 0); + }); + } + + public activateSelection(): Promise { + const prom = this.selection.selected.map((action) => { + return this.mgmtService.reactivateAction(action.id); + }); + + return Promise.all(prom) + .then(() => { + this.selection.clear(); + this.toast.showInfo('FLOWS.TOAST.ACTIONREACTIVATED', true); + this.getData(10, 0); + }) + .catch((error) => { + this.selection.clear(); + this.toast.showError(error); + this.getData(10, 0); + }); + } } diff --git a/console/src/app/pages/actions/actions.component.html b/console/src/app/pages/actions/actions.component.html index a36ef26e38..e1c9905833 100644 --- a/console/src/app/pages/actions/actions.component.html +++ b/console/src/app/pages/actions/actions.component.html @@ -2,6 +2,9 @@

{{ 'FLOWS.TITLE' | translate }}

{{'FLOWS.DESCRIPTION' | translate }}

+ {{'FLOWS.ACTIONSMAX' | translate: ({value: maxActions}) }} + + @@ -44,11 +47,15 @@ {{'FLOWS.TRIGGERTYPES.'+trigger.triggerType | translate}} -
-
+
- {{action.name}} + {{action.name}} + + + {{'FLOWS.STATES.'+action.state | translate}}
diff --git a/console/src/app/pages/actions/actions.component.scss b/console/src/app/pages/actions/actions.component.scss index 72e9bca206..61bbe09752 100644 --- a/console/src/app/pages/actions/actions.component.scss +++ b/console/src/app/pages/actions/actions.component.scss @@ -18,7 +18,7 @@ h1 { } i { - margin-left: .5rem; + margin-left: 0.5rem; } } @@ -29,8 +29,8 @@ h1 { .flow-type { padding: 1rem 1rem; - margin: .5rem 0; - border-radius: .5rem; + margin: 0.5rem 0; + border-radius: 0.5rem; display: flex; align-items: center; justify-content: space-between; @@ -39,7 +39,7 @@ h1 { .topelements { border: 3px solid var(--color-main); border-radius: 1rem; - padding: 0 .5rem; + padding: 0 0.5rem; } .trigger-wrapper { @@ -57,13 +57,13 @@ h1 { } .trigger { - padding: .5rem 1rem; - border-radius: .5rem; + padding: 0.5rem 1rem; + border-radius: 0.5rem; display: flex; align-items: center; background: var(--color-main); color: white; - margin: .5rem 0; + margin: 0.5rem 0; min-height: 40px; .icon { @@ -74,18 +74,27 @@ h1 { flex: 1; } - .action-wrapper { - padding: 0 .5rem; + .flow-action-wrapper { + padding: 0 0.5rem; + margin: 0; - .action { + .flow-action { display: flex; align-items: center; font-size: 14px; - padding: .5rem 0; + padding: 0.5rem 0; cursor: move; + .flow-action-name { + margin-right: 1rem; + } + + .fill-space { + flex: 1; + } + i { - margin-right: .5rem; + margin-right: 0.5rem; } } } @@ -105,13 +114,13 @@ h1 { display: flex; align-items: center; font-size: 14px; - border-radius: .5rem; - padding: 0 .5rem; + border-radius: 0.5rem; + padding: 0 0.5rem; background-color: var(--color-main); - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, .2), 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12); + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); i { - margin-right: .5rem; + margin-right: 0.5rem; } } @@ -120,5 +129,5 @@ h1 { } .cdk-drag-animating { - transition: transform 250ms cubic-bezier(0, 0, .2, 1); + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } diff --git a/console/src/app/pages/actions/actions.component.ts b/console/src/app/pages/actions/actions.component.ts index 15152b12bc..11b652037a 100644 --- a/console/src/app/pages/actions/actions.component.ts +++ b/console/src/app/pages/actions/actions.component.ts @@ -4,7 +4,8 @@ import { FormControl } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { InfoSectionType } from 'src/app/modules/info-section/info-section.component'; import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; -import { Action, Flow, FlowType, TriggerType } from 'src/app/proto/generated/zitadel/action_pb'; +import { Action, ActionState, Flow, FlowType, TriggerType } from 'src/app/proto/generated/zitadel/action_pb'; +import { ActionsAllowed } from 'src/app/proto/generated/zitadel/features_pb'; import { SetTriggerActionsRequest } from 'src/app/proto/generated/zitadel/management_pb'; import { ManagementService } from 'src/app/services/mgmt.service'; import { ToastService } from 'src/app/services/toast.service'; @@ -26,14 +27,28 @@ export class ActionsComponent { public selection: Action.AsObject[] = []; public InfoSectionType: any = InfoSectionType; + public maxActions: number | null = null; + public ActionState: any = ActionState; constructor(private mgmtService: ManagementService, private dialog: MatDialog, private toast: ToastService) { + this.mgmtService.getFeatures().then((featuresResp) => { + if (featuresResp && featuresResp.features) { + const features = featuresResp.features; + this.maxActions = + features && features.actionsAllowed === ActionsAllowed.ACTIONS_ALLOWED_MAX + ? features.maxActions + : features.actionsAllowed === ActionsAllowed.ACTIONS_ALLOWED_MAX + ? null + : 0; + } + }); this.loadFlow(); } private loadFlow() { this.mgmtService.getFlow(this.flowType).then((flowResponse) => { if (flowResponse.flow) this.flow = flowResponse.flow; + console.log(this.flow); }); } @@ -92,7 +107,6 @@ export class ActionsComponent { } saveFlow(index: number) { - console.log(this.flow.triggerActionsList[index].actionsList.map((action) => action.id)); this.mgmtService .setTriggerActions( this.flow.triggerActionsList[index].actionsList.map((action) => action.id), diff --git a/console/src/app/services/mgmt.service.ts b/console/src/app/services/mgmt.service.ts index 53f087fcbe..4e9e2ce470 100644 --- a/console/src/app/services/mgmt.service.ts +++ b/console/src/app/services/mgmt.service.ts @@ -74,6 +74,8 @@ import { ClearFlowResponse, CreateActionRequest, CreateActionResponse, + DeactivateActionRequest, + DeactivateActionResponse, DeactivateAppRequest, DeactivateAppResponse, DeactivateOrgIDPRequest, @@ -238,6 +240,8 @@ import { ListUserMetadataResponse, ListUsersRequest, ListUsersResponse, + ReactivateActionRequest, + ReactivateActionResponse, ReactivateAppRequest, ReactivateAppResponse, ReactivateOrgIDPRequest, @@ -919,6 +923,18 @@ export class ManagementService { return this.grpcService.mgmt.deleteAction(req, null).then((resp) => resp.toObject()); } + public deactivateAction(id: string): Promise { + const req = new DeactivateActionRequest(); + req.setId(id); + return this.grpcService.mgmt.deactivateAction(req, null).then((resp) => resp.toObject()); + } + + public reactivateAction(id: string): Promise { + const req = new ReactivateActionRequest(); + req.setId(id); + return this.grpcService.mgmt.reactivateAction(req, null).then((resp) => resp.toObject()); + } + public listActions( limit?: number, offset?: number, diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 72f7f607e8..51e1dcc26d 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -564,6 +564,7 @@ "FLOWTYPE": "Flow Typ", "TRIGGERTYPE": "Trigger Typ", "ACTIONS": "Aktionen", + "ACTIONSMAX": "Basierend auf Ihrem Tier steht Ihnen eine begrenzte Anzahl von Aktionen ({{value}}) zur Verfügung. Stellen Sie sicher, dass Sie diejenigen deaktivieren, die Sie nicht benötigen, oder erwägen Sie ein Upgrade.", "DIALOG": { "ADD": { "TITLE": "Aktion erstellen" @@ -581,7 +582,9 @@ } }, "TOAST": { - "ACTIONSSET": "Aktionen gesetzt" + "ACTIONSSET": "Aktionen gesetzt", + "ACTIONREACTIVATED": "Aktionen erfolgreich reaktiviert", + "ACTIONDEACTIVATED": "Aktionen erfolgreich deaktiviert" } }, "IAM": { @@ -756,7 +759,16 @@ "CUSTOMTEXTMESSAGE": "Benutzerdefinierte Benachrichtigungstexte", "PRIVACYPOLICY": "Benutzerdefinierte Datenschutzrichtlinie und AGB", "METADATAUSER": "User Metadata", - "FLOWS": "Aktionen und Abläufe" + "FLOWS": "Aktionen und Abläufe", + "FLOW": { + "ACTIONSALLOWED": { + "0": "Nicht erlaubt", + "1": "Limitierte Aktionen", + "2": "Unlimitiert" + }, + "TYPE": "Verwendungsart", + "COUNT": "Anzahl" + } }, "TIERSTATES": { "0": "Aktiv", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 47c134008f..c669af9d93 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -564,6 +564,7 @@ "FLOWTYPE": "Flow Type", "TRIGGERTYPE": "Trigger Type", "ACTIONS": "Actions", + "ACTIONSMAX": "Based on your Tier, you have available a limited Number of Actions ({{value}}). Make sure to deaktivate those you are not in need or consider upgrading your tier.", "DIALOG": { "ADD": { "TITLE": "Create an Action" @@ -581,7 +582,9 @@ } }, "TOAST": { - "ACTIONSSET": "Actions set" + "ACTIONSSET": "Actions set", + "ACTIONREACTIVATED": "Actions reactivated with success", + "ACTIONDEACTIVATED": "Actions deactivated with success" } }, "IAM": { @@ -756,7 +759,16 @@ "CUSTOMTEXTMESSAGE": "Custom notification mail texts", "PRIVACYPOLICY": "Custom Privacy Policy and TOS Links", "METADATAUSER": "User Metadata", - "FLOWS": "Actions and Flows" + "FLOWS": "Actions and Flows", + "FLOW": { + "ACTIONSALLOWED": { + "0": "Not allowed", + "1": "Limited Actions", + "2": "Unlimited Actions" + }, + "TYPE": "Type of use", + "COUNT": "Number" + } }, "TIERSTATES": { "0": "Active", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 8c0b625187..20de03f602 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -564,6 +564,7 @@ "FLOWTYPE": "Tipo processo", "TRIGGERTYPE": "Tipo trigger", "ACTIONS": "Azioni", + "ACTIONSMAX": "In base al tuo tier, hai a disposizione un numero limitato di azioni ({{value}}). Assicurati di disattivare quelli di cui non hai bisogno o considera di fare un upgrade.", "DIALOG": { "ADD": { "TITLE": "Crea azione" @@ -581,7 +582,9 @@ } }, "TOAST": { - "ACTIONSSET": "Azioni salvate!" + "ACTIONSSET": "Azioni salvate!", + "ACTIONREACTIVATED": "Azioni riattivati con successo", + "ACTIONDEACTIVATED": "Azioni disattivati con successo" } }, "IAM": { @@ -756,7 +759,16 @@ "CUSTOMTEXTMESSAGE": "Testi email personalizzati", "PRIVACYPOLICY": "Link personalizzati all'informativa sulla privacy e ai TOS", "METADATAUSER": "Metadati utente", - "FLOWS": "Azioni e processi" + "FLOWS": "Azioni e processi", + "FLOW": { + "ACTIONSALLOWED": { + "0": "Non abilitato", + "1": "Numero limitato", + "2": "Illimitato" + }, + "COUNT": "Anzahl", + "TYPE": "Tipo di utilizzo" + } }, "TIERSTATES": { "0": "Attivo",