diff --git a/console/src/app/modules/org-table/org-table.component.html b/console/src/app/modules/org-table/org-table.component.html
index 695837b38b..245274928f 100644
--- a/console/src/app/modules/org-table/org-table.component.html
+++ b/console/src/app/modules/org-table/org-table.component.html
@@ -52,7 +52,12 @@
{{ 'ORG.PAGES.NAME' | translate }}
|
- {{ org.name }} |
+
+ {{ org.name }}{{
+ 'ORG.PAGES.DEFAULTLABEL' | translate
+ }}
+ |
@@ -88,6 +93,17 @@
+
+ |
+
+
+
+
+ |
+
+
diff --git a/console/src/app/modules/org-table/org-table.component.scss b/console/src/app/modules/org-table/org-table.component.scss
index b2d15ea57b..cc165bacc9 100644
--- a/console/src/app/modules/org-table/org-table.component.scss
+++ b/console/src/app/modules/org-table/org-table.component.scss
@@ -9,6 +9,10 @@ td {
}
}
+ .orgdefaultlabel {
+ margin-left: 0.5rem;
+ }
+
&:hover {
.cpy-button {
visibility: visible;
diff --git a/console/src/app/modules/org-table/org-table.component.ts b/console/src/app/modules/org-table/org-table.component.ts
index 833a46201e..2a06fb99f8 100644
--- a/console/src/app/modules/org-table/org-table.component.ts
+++ b/console/src/app/modules/org-table/org-table.component.ts
@@ -11,6 +11,8 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
import { PaginatorComponent } from '../paginator/paginator.component';
+import { AdminService } from 'src/app/services/admin.service';
+import { ManagementService } from 'src/app/services/mgmt.service';
enum OrgListSearchKey {
NAME = 'NAME',
@@ -30,7 +32,7 @@ export class OrgTableComponent {
@ViewChild('input') public filter!: Input;
public dataSource: MatTableDataSource = new MatTableDataSource([]);
- public displayedColumns: string[] = ['name', 'state', 'primaryDomain', 'creationDate', 'changeDate'];
+ public displayedColumns: string[] = ['name', 'state', 'primaryDomain', 'creationDate', 'changeDate', 'actions'];
private loadingSubject: BehaviorSubject = new BehaviorSubject(false);
public loading$: Observable = this.loadingSubject.asObservable();
public activeOrg!: Org.AsObject;
@@ -50,10 +52,13 @@ export class OrgTableComponent {
offset: 0,
queries: [],
});
+ public defaultOrgId: string = '';
private requestOrgsObservable$ = this.requestOrgs$.pipe(takeUntil(this.destroy$));
constructor(
private authService: GrpcAuthService,
+ private mgmtService: ManagementService,
+ private adminService: AdminService,
private router: Router,
private toast: ToastService,
private _liveAnnouncer: LiveAnnouncer,
@@ -65,6 +70,10 @@ export class OrgTableComponent {
this.requestOrgsObservable$.pipe(switchMap((req) => this.loadOrgs(req))).subscribe((orgs) => {
this.dataSource = new MatTableDataSource(orgs);
});
+
+ this.mgmtService.getIAM().then((iam) => {
+ this.defaultOrgId = iam.defaultOrgId;
+ });
}
public loadOrgs(request: Request): Observable {
@@ -111,6 +120,18 @@ export class OrgTableComponent {
}
}
+ public setDefaultOrg(org: Org.AsObject) {
+ this.adminService
+ .setDefaultOrg(org.id)
+ .then(() => {
+ this.toast.showInfo('ORG.PAGES.DEFAULTORGSET', true);
+ this.defaultOrgId = org.id;
+ })
+ .catch((error) => {
+ this.toast.showError(error);
+ });
+ }
+
public applySearchQuery(searchQueries: OrgQuery[]): void {
this.searchQueries = searchQueries;
this.requestOrgs$.next({
diff --git a/console/src/app/modules/org-table/org-table.module.ts b/console/src/app/modules/org-table/org-table.module.ts
index 58d96b1d8e..55f2d256ef 100644
--- a/console/src/app/modules/org-table/org-table.module.ts
+++ b/console/src/app/modules/org-table/org-table.module.ts
@@ -20,6 +20,7 @@ import { InputModule } from '../input/input.module';
import { PaginatorModule } from '../paginator/paginator.module';
import { RefreshTableModule } from '../refresh-table/refresh-table.module';
import { OrgTableComponent } from './org-table.component';
+import { TableActionsModule } from '../table-actions/table-actions.module';
@NgModule({
declarations: [OrgTableComponent],
@@ -33,6 +34,7 @@ import { OrgTableComponent } from './org-table.component';
TimestampToDatePipeModule,
LocalizedDatePipeModule,
MatSortModule,
+ TableActionsModule,
MatIconModule,
PaginatorModule,
HasRoleModule,
diff --git a/console/src/app/services/admin.service.ts b/console/src/app/services/admin.service.ts
index 2244372bf0..4b203ed93f 100644
--- a/console/src/app/services/admin.service.ts
+++ b/console/src/app/services/admin.service.ts
@@ -222,6 +222,8 @@ import {
GetCustomPasswordChangeMessageTextRequest,
AddNotificationPolicyRequest,
AddNotificationPolicyResponse,
+ SetDefaultOrgRequest,
+ SetDefaultOrgResponse,
} from '../proto/generated/zitadel/admin_pb';
import { SearchQuery } from '../proto/generated/zitadel/member_pb';
import { ListQuery } from '../proto/generated/zitadel/object_pb';
@@ -233,6 +235,13 @@ import { GrpcService } from './grpc.service';
export class AdminService {
constructor(private readonly grpcService: GrpcService) {}
+ public setDefaultOrg(orgId: string): Promise {
+ const req = new SetDefaultOrgRequest();
+ req.setOrgId(orgId);
+
+ return this.grpcService.admin.setDefaultOrg(req, null).then((resp) => resp.toObject());
+ }
+
public listEvents(req: ListEventsRequest): Promise {
return this.grpcService.admin.listEvents(req, null).then((resp) => resp);
}
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json
index 202a29b5dd..0797398537 100644
--- a/console/src/assets/i18n/de.json
+++ b/console/src/assets/i18n/de.json
@@ -843,6 +843,9 @@
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Geben Sie den Namen der neuen Organisation ein.",
"ORGDETAILUSER_TITLE": "Organisationsbesitzer hinzufügen",
"DELETE": "Organisation löschen",
+ "DEFAULTLABEL": "Standard",
+ "SETASDEFAULT": "Als Standardorganisation festlegen",
+ "DEFAULTORGSET": "Standardorganisation erfolgreich geändert",
"RENAME": {
"ACTION": "Umbenennen",
"TITLE": "Organisation umbenennen",
diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json
index 4e16a7f8b1..9bbf34972d 100644
--- a/console/src/assets/i18n/en.json
+++ b/console/src/assets/i18n/en.json
@@ -843,6 +843,9 @@
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Enter the name of your new organization.",
"ORGDETAILUSER_TITLE": "Configure Organization Owner",
"DELETE": "Delete organization",
+ "DEFAULTLABEL": "Default",
+ "SETASDEFAULT": "Set as default organization",
+ "DEFAULTORGSET": "Default organization changed successfully",
"RENAME": {
"ACTION": "Rename",
"TITLE": "Rename Organization",
diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json
index 48f1fe5c51..7e7e840adf 100644
--- a/console/src/assets/i18n/fr.json
+++ b/console/src/assets/i18n/fr.json
@@ -843,6 +843,9 @@
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Saisissez le nom de votre nouvelle organisation.",
"ORGDETAILUSER_TITLE": "Configurer le propriétaire de l'organisation",
"DELETE": "Supprimer l'organisation",
+ "DEFAULTLABEL": "Défaut",
+ "SETASDEFAULT": "Définir comme organisation par défaut",
+ "DEFAULTORGSET": "L'organisation par défaut a été modifiée avec succès",
"RENAME": {
"ACTION": "Renommer",
"TITLE": "Renommer l'organisation",
diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json
index ad5c70d8b4..a99a507a29 100644
--- a/console/src/assets/i18n/it.json
+++ b/console/src/assets/i18n/it.json
@@ -844,6 +844,9 @@
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Inserisci il nome della tua nuova organizzazione.",
"ORGDETAILUSER_TITLE": "Configurare il proprietario dell'organizzazione",
"DELETE": "Elimina organizzazione",
+ "DEFAULTLABEL": "Standard",
+ "SETASDEFAULT": "Imposta come organizzazione predefinita",
+ "DEFAULTORGSET": "Organizzazione predefinita cambiata con successo",
"RENAME": {
"ACTION": "Rinomina",
"TITLE": "Rinomina organizzazione",
diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json
index 051cc7e1ee..25fe8a6750 100644
--- a/console/src/assets/i18n/zh.json
+++ b/console/src/assets/i18n/zh.json
@@ -843,6 +843,9 @@
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "输入新组织的名称。",
"ORGDETAILUSER_TITLE": "配置组织所有者",
"DELETE": "删除组织",
+ "DEFAULTLABEL": "默认情况下",
+ "SETASDEFAULT": "设置为默认组织",
+ "DEFAULTORGSET": "默认的组织已经改变",
"RENAME": {
"ACTION": "改名",
"TITLE": "重命名组织",
diff --git a/e2e/cypress/e2e/organization/organizations.cy.ts b/e2e/cypress/e2e/organization/organizations.cy.ts
index 1fbcb09869..f6ce9721a7 100644
--- a/e2e/cypress/e2e/organization/organizations.cy.ts
+++ b/e2e/cypress/e2e/organization/organizations.cy.ts
@@ -1,4 +1,4 @@
-import { ensureOrgExists } from 'support/api/orgs';
+import { ensureOrgExists, ensureOrgIsDefault, isDefaultOrg } from 'support/api/orgs';
import { apiAuth } from '../../support/api/apiauth';
import { v4 as uuidv4 } from 'uuid';
@@ -33,6 +33,40 @@ describe('organizations', () => {
});
});
+ const orgOverviewPath = `/orgs`;
+ const initialDefaultOrg = 'e2eorgolddefault';
+ const orgNameForNewDefault = 'e2eorgnewdefault';
+
+ describe('set default org', () => {
+ beforeEach(() => {
+ apiAuth()
+ .as('api')
+ .then((api) => {
+ ensureOrgExists(api, orgNameForNewDefault)
+ .as('newDefaultOrgId')
+ .then(() => {
+ ensureOrgExists(api, initialDefaultOrg)
+ .as('defaultOrg')
+ .then((id) => {
+ ensureOrgIsDefault(api, id)
+ .as('orgWasDefault')
+ .then(() => {
+ cy.visit(`${orgOverviewPath}`).as('orgsite');
+ });
+ });
+ });
+ });
+ });
+
+ it('should rename the organization', function () {
+ const rowSelector = `tr:contains(${orgNameForNewDefault})`;
+ cy.get(rowSelector).find('[data-e2e="table-actions-button"]').click({ force: true });
+ cy.get('[data-e2e="set-default-button"]', { timeout: 1000 }).should('be.visible').click();
+ cy.shouldConfirmSuccess();
+ isDefaultOrg(this.api, this.newDefaultOrgId);
+ });
+ });
+
it('should add an organization with the personal account as org owner');
describe('changing the current organization', () => {
it('should update displayed organization details');
diff --git a/e2e/cypress/support/api/orgs.ts b/e2e/cypress/support/api/orgs.ts
index 1141f1caea..ceaa98b5c1 100644
--- a/e2e/cypress/support/api/orgs.ts
+++ b/e2e/cypress/support/api/orgs.ts
@@ -2,6 +2,7 @@ import { ensureSomething } from './ensure';
import { searchSomething } from './search';
import { API } from './types';
import { host } from '../login/users';
+import { requestHeaders } from './apiauth';
export function ensureOrgExists(api: API, name: string): Cypress.Chainable {
return ensureSomething(
@@ -23,6 +24,51 @@ export function ensureOrgExists(api: API, name: string): Cypress.Chainable {
+ console.log('huhu', orgId);
+ return cy
+ .request({
+ method: 'GET',
+ url: encodeURI(`${api.mgmtBaseURL}/iam`),
+ headers: requestHeaders(api, orgId),
+ })
+ .then((res) => {
+ const { defaultOrgId } = res.body;
+ expect(defaultOrgId).to.equal(orgId);
+ return defaultOrgId === orgId;
+ });
+}
+
+export function ensureOrgIsDefault(api: API, orgId: number): Cypress.Chainable {
+ return cy
+ .request({
+ method: 'GET',
+ url: encodeURI(`${api.mgmtBaseURL}/iam`),
+ headers: requestHeaders(api, orgId),
+ })
+ .then((res) => {
+ return res.body;
+ })
+ .then(({ defaultOrgId }) => {
+ if (defaultOrgId === orgId) {
+ return true;
+ } else {
+ return cy
+ .request({
+ method: 'PUT',
+ url: `${api.adminBaseURL}/orgs/default/${orgId}`,
+ headers: requestHeaders(api, orgId),
+ failOnStatusCode: true,
+ followRedirect: false,
+ })
+ .then((cRes) => {
+ expect(cRes.status).to.equal(200);
+ return !!cRes.body;
+ });
+ }
+ });
+}
+
export function getOrgUnderTest(api: API): Cypress.Chainable {
return searchSomething(api, `${api.mgmtBaseURL}/orgs/me`, 'GET', (res) => {
return { entity: res.org, id: res.org.id, sequence: parseInt(res.org.details.sequence) };