mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 23:45:37 +00:00
[EC-16] Update Group tab to use table component and show collections.
This commit is contained in:
@@ -1,18 +1,21 @@
|
||||
<div class="container page-content">
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "groups" | i18n }}</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<div>
|
||||
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||
<input
|
||||
type="search"
|
||||
class="form-control form-control-sm"
|
||||
id="search"
|
||||
placeholder="{{ 'search' | i18n }}"
|
||||
[(ngModel)]="searchText"
|
||||
/>
|
||||
<div class="tw-ml-auto tw-flex tw-items-center">
|
||||
<div class="tw-mr-2">
|
||||
<label class="sr-only">{{ "search" | i18n }}</label>
|
||||
<div class="tw-flex tw-items-center">
|
||||
<i class="bwi bwi-search bwi-fw tw-z-20 -tw-mr-7 tw-text-muted" aria-hidden="true"></i>
|
||||
<input
|
||||
bitInput
|
||||
type="search"
|
||||
placeholder="{{ 'search' | i18n }}"
|
||||
class="tw-rounded-l tw-pl-9"
|
||||
[(ngModel)]="searchText"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
|
||||
<button bitButton type="button" buttonType="primary" (click)="add()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newGroup" | i18n }}
|
||||
</button>
|
||||
@@ -26,53 +29,105 @@
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!loading &&
|
||||
(isPaging() ? pagedGroups : (groups | search: searchText:'name':'id')) as searchedGroups
|
||||
"
|
||||
>
|
||||
<p *ngIf="!searchedGroups.length">{{ "noGroupsInList" | i18n }}</p>
|
||||
<table
|
||||
class="table table-hover table-list"
|
||||
*ngIf="searchedGroups.length"
|
||||
infiniteScroll
|
||||
<ng-container *ngIf="!loading && visibleGroups">
|
||||
<p *ngIf="!visibleGroups.length">{{ "noGroupsInList" | i18n }}</p>
|
||||
<bit-table
|
||||
*ngIf="visibleGroups.length"
|
||||
infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let g of searchedGroups">
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(g)">{{ g.name }}</a>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-20">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="tw-mr-2"
|
||||
(change)="toggleAllVisible($event)"
|
||||
id="selectAll"
|
||||
/>
|
||||
<label class="tw-mb-0" for="selectAll">{{ "all" | i18n }}</label>
|
||||
</th>
|
||||
<th bitCell>{{ "name" | i18n }}</th>
|
||||
<th bitCell>{{ "collections" | i18n }}</th>
|
||||
<th bitCell class="tw-w-10">
|
||||
<button
|
||||
class="tw-border-none tw-bg-transparent tw-text-muted"
|
||||
[bitMenuTriggerFor]="headerMenu"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-ellipsis-v bwi-lg tw-font-bold"></i>
|
||||
</button>
|
||||
|
||||
<bit-menu #headerMenu>
|
||||
<button type="button" bitMenuItem (click)="deleteAllSelected()">
|
||||
<span class="tw-text-danger"
|
||||
><i aria-hidden="true" class="bwi bwi-trash"></i> Delete</span
|
||||
>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="users(g)">
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "users" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(g)">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</bit-menu>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-container body>
|
||||
<tr bitRow *ngFor="let g of visibleGroups">
|
||||
<td bitCell (click)="check(g)">
|
||||
<input type="checkbox" [(ngModel)]="g.checked" />
|
||||
</td>
|
||||
<td bitCell class="tw-text-lg tw-font-bold" (click)="edit(g)">
|
||||
<button (click)="edit(g)" bitLink>
|
||||
{{ g.name }}
|
||||
</button>
|
||||
</td>
|
||||
<td bitCell (click)="edit(g)">
|
||||
<span
|
||||
*ngFor="let cName of g.collectionNames.slice(0, maxCollections); let last = last"
|
||||
bitBadge
|
||||
badgeType="secondary"
|
||||
class="tw-mx-1"
|
||||
>
|
||||
{{ cName }} <span class="sr-only" *ngIf="!last">, </span>
|
||||
</span>
|
||||
<span
|
||||
*ngIf="g.collectionNames.length > maxCollections"
|
||||
bitBadge
|
||||
badgeType="secondary"
|
||||
class="tw-mx-1"
|
||||
>
|
||||
{{ "plusNMore" | i18n: (g.collectionNames.length - maxCollections).toString() }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
class="tw-border-none tw-bg-transparent tw-text-muted"
|
||||
[bitMenuTriggerFor]="rowMenu"
|
||||
type="button"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-ellipsis-v bwi-lg tw-font-bold"></i>
|
||||
</button>
|
||||
|
||||
<bit-menu #rowMenu>
|
||||
<button type="button" bitMenuItem (click)="edit(g)">
|
||||
<i aria-hidden="true" class="bwi bwi-pencil-square"></i> Edit Info
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(g)">
|
||||
<i aria-hidden="true" class="bwi bwi-user"></i> Members
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(g)">
|
||||
<i aria-hidden="true" class="bwi bwi-collection"></i> Collections
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="delete(g)">
|
||||
<span class="tw-text-danger"
|
||||
><i aria-hidden="true" class="bwi bwi-trash"></i> Delete</span
|
||||
>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</bit-table>
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #usersTemplate></ng-template>
|
||||
|
||||
@@ -3,18 +3,34 @@ import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { GroupResponse } from "@bitwarden/common/models/response/groupResponse";
|
||||
import { CollectionData } from "@bitwarden/common/models/data/collectionData";
|
||||
import { Collection } from "@bitwarden/common/models/domain/collection";
|
||||
import { OrganizationUserBulkRequest } from "@bitwarden/common/models/request/organizationUserBulkRequest";
|
||||
import { CollectionDetailsResponse } from "@bitwarden/common/models/response/collectionResponse";
|
||||
import { GroupDetailsResponse } from "@bitwarden/common/models/response/groupResponse";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
|
||||
|
||||
import { EntityUsersComponent } from "./entity-users.component";
|
||||
import { GroupAddEditComponent } from "./group-add-edit.component";
|
||||
|
||||
type CollectionViewMap = {
|
||||
[id: string]: CollectionView;
|
||||
};
|
||||
|
||||
type GroupDetailsView = Partial<GroupDetailsResponse> & {
|
||||
checked?: boolean;
|
||||
collectionNames?: string[];
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: "app-org-groups",
|
||||
templateUrl: "groups.component.html",
|
||||
@@ -26,16 +42,45 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
|
||||
loading = true;
|
||||
organizationId: string;
|
||||
groups: GroupResponse[];
|
||||
pagedGroups: GroupResponse[];
|
||||
searchText: string;
|
||||
groups: GroupDetailsView[];
|
||||
collectionMap: CollectionViewMap = {};
|
||||
selectAll = false;
|
||||
|
||||
protected didScroll = false;
|
||||
protected pageSize = 100;
|
||||
protected maxCollections = 2;
|
||||
|
||||
private pagedGroupsCount = 0;
|
||||
private pagedGroups: GroupDetailsView[];
|
||||
private searchedGroups: GroupDetailsView[];
|
||||
private _searchText: string;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
get searchText() {
|
||||
return this._searchText;
|
||||
}
|
||||
set searchText(value: string) {
|
||||
this._searchText = value;
|
||||
// Manually update as we are not using the search pipe in the template
|
||||
this.updateSearchedGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of groups that should be visible in the table.
|
||||
* This is needed as there are two modes (paging/searching) and
|
||||
* we need a reference to the currently visible groups for
|
||||
* the Select All checkbox
|
||||
*/
|
||||
get visibleGroups() {
|
||||
if (this.isPaging()) {
|
||||
return this.pagedGroups;
|
||||
}
|
||||
if (this.isSearching()) {
|
||||
return this.searchedGroups;
|
||||
}
|
||||
return this.groups;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -43,7 +88,9 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
private modalService: ModalService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private searchService: SearchService,
|
||||
private logService: LogService
|
||||
private logService: LogService,
|
||||
private collectionService: CollectionService,
|
||||
private searchPipe: SearchPipe
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -51,6 +98,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
.pipe(
|
||||
concatMap(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.loadCollections();
|
||||
await this.load();
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
@@ -76,12 +124,38 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
async load() {
|
||||
const response = await this.apiService.getGroups(this.organizationId);
|
||||
const groups = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
groups.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
this.groups = groups;
|
||||
this.groups = groups
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"))
|
||||
.map<GroupDetailsView>((g) => ({
|
||||
...g,
|
||||
checked: false,
|
||||
collectionNames: g.collections
|
||||
.map((c) => this.collectionMap[c.id]?.name)
|
||||
.sort(this.i18nService.collator?.compare),
|
||||
}));
|
||||
this.resetPaging();
|
||||
this.updateSearchedGroups();
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
private updateSearchedGroups() {
|
||||
if (this.searchService.isSearchable(this.searchText)) {
|
||||
// Making use of the pipe in the component as we need know which groups where filtered
|
||||
this.searchedGroups = this.searchPipe.transform(this.groups, this.searchText, "name", "id");
|
||||
}
|
||||
}
|
||||
|
||||
async loadCollections() {
|
||||
const response = await this.apiService.getCollections(this.organizationId);
|
||||
const collections = response.data.map(
|
||||
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
|
||||
);
|
||||
const decryptedCollections = await this.collectionService.decryptMany(collections);
|
||||
|
||||
// Convert to an object using collection Ids as keys for faster name lookups
|
||||
decryptedCollections.forEach((c) => (this.collectionMap[c.id] = c));
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (!this.groups || this.groups.length <= this.pageSize) {
|
||||
return;
|
||||
@@ -100,7 +174,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
this.didScroll = this.pagedGroups.length > this.pageSize;
|
||||
}
|
||||
|
||||
async edit(group: GroupResponse) {
|
||||
async edit(group: GroupDetailsResponse) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
GroupAddEditComponent,
|
||||
this.addEditModalRef,
|
||||
@@ -113,7 +187,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
comp.onDeletedGroup.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
modal.close();
|
||||
this.removeGroup(group);
|
||||
this.removeGroup(group.id);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -123,7 +197,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
this.edit(null);
|
||||
}
|
||||
|
||||
async delete(group: GroupResponse) {
|
||||
async delete(group: GroupDetailsResponse) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deleteGroupConfirmation"),
|
||||
group.name,
|
||||
@@ -142,13 +216,45 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
null,
|
||||
this.i18nService.t("deletedGroupId", group.name)
|
||||
);
|
||||
this.removeGroup(group);
|
||||
this.removeGroup(group.id);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async users(group: GroupResponse) {
|
||||
async deleteAllSelected() {
|
||||
const groupsToDelete = this.groups.filter((g) => g.checked);
|
||||
|
||||
if (groupsToDelete.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deleteMessage = groupsToDelete.map((g) => g.name).join(", ");
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
deleteMessage,
|
||||
this.i18nService.t("deleteMultipleGroupsConfirmation", groupsToDelete.length.toString()),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.apiService.deleteManyGroups(
|
||||
this.organizationId,
|
||||
new OrganizationUserBulkRequest(groupsToDelete.map((g) => g.id))
|
||||
);
|
||||
this.platformUtilsService.showToast("success", null, `Delete ${result.data.length} groups!`);
|
||||
|
||||
groupsToDelete.forEach((g) => this.removeGroup(g.id));
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async users(group: GroupDetailsResponse) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
EntityUsersComponent,
|
||||
this.usersModalRef,
|
||||
@@ -174,6 +280,14 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
return this.searchService.isSearchable(this.searchText);
|
||||
}
|
||||
|
||||
check(group: GroupDetailsView) {
|
||||
group.checked = !group.checked;
|
||||
}
|
||||
|
||||
toggleAllVisible(event: Event) {
|
||||
this.visibleGroups.forEach((g) => (g.checked = (event.target as HTMLInputElement).checked));
|
||||
}
|
||||
|
||||
isPaging() {
|
||||
const searching = this.isSearching();
|
||||
if (searching && this.didScroll) {
|
||||
@@ -182,11 +296,12 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
return !searching && this.groups && this.groups.length > this.pageSize;
|
||||
}
|
||||
|
||||
private removeGroup(group: GroupResponse) {
|
||||
const index = this.groups.indexOf(group);
|
||||
private removeGroup(id: string) {
|
||||
const index = this.groups.findIndex((g) => g.id === id);
|
||||
if (index > -1) {
|
||||
this.groups.splice(index, 1);
|
||||
this.resetPaging();
|
||||
this.updateSearchedGroups();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DragDropModule } from "@angular/cdk/drag-drop";
|
||||
import { DatePipe, CommonModule } from "@angular/common";
|
||||
import { CommonModule, DatePipe } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { RouterModule } from "@angular/router";
|
||||
@@ -12,10 +12,12 @@ import {
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
FormFieldModule,
|
||||
SubmitButtonModule,
|
||||
MenuModule,
|
||||
TabsModule,
|
||||
IconModule,
|
||||
LinkModule,
|
||||
MenuModule,
|
||||
SubmitButtonModule,
|
||||
TableModule,
|
||||
TabsModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
// Register the locales for the application
|
||||
@@ -48,6 +50,8 @@ import "./locales";
|
||||
SubmitButtonModule,
|
||||
IconModule,
|
||||
TabsModule,
|
||||
TableModule,
|
||||
LinkModule,
|
||||
],
|
||||
exports: [
|
||||
CommonModule,
|
||||
@@ -68,6 +72,8 @@ import "./locales";
|
||||
SubmitButtonModule,
|
||||
IconModule,
|
||||
TabsModule,
|
||||
TableModule,
|
||||
LinkModule,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
bootstrap: [],
|
||||
|
||||
@@ -2326,6 +2326,15 @@
|
||||
"deleteGroupConfirmation": {
|
||||
"message": "Are you sure you want to delete this group?"
|
||||
},
|
||||
"deleteMultipleGroupsConfirmation": {
|
||||
"message": "Are you sure you want to delete the following $QUANTITY$ group(s)?",
|
||||
"placeholders": {
|
||||
"quantity": {
|
||||
"content": "$1",
|
||||
"example": "3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removeUserConfirmation": {
|
||||
"message": "Are you sure you want to remove this user?"
|
||||
},
|
||||
@@ -5370,5 +5379,14 @@
|
||||
},
|
||||
"numberOfUsers": {
|
||||
"message": "Number of users"
|
||||
},
|
||||
"plusNMore": {
|
||||
"message": "+ $QUANTITY$ more",
|
||||
"placeholders": {
|
||||
"quantity": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,12 +334,16 @@ export abstract class ApiService {
|
||||
) => Promise<any>;
|
||||
|
||||
getGroupDetails: (organizationId: string, id: string) => Promise<GroupDetailsResponse>;
|
||||
getGroups: (organizationId: string) => Promise<ListResponse<GroupResponse>>;
|
||||
getGroups: (organizationId: string) => Promise<ListResponse<GroupDetailsResponse>>;
|
||||
getGroupUsers: (organizationId: string, id: string) => Promise<string[]>;
|
||||
postGroup: (organizationId: string, request: GroupRequest) => Promise<GroupResponse>;
|
||||
putGroup: (organizationId: string, id: string, request: GroupRequest) => Promise<GroupResponse>;
|
||||
putGroupUsers: (organizationId: string, id: string, request: string[]) => Promise<any>;
|
||||
deleteGroup: (organizationId: string, id: string) => Promise<any>;
|
||||
deleteManyGroups: (
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRequest
|
||||
) => Promise<ListResponse<GroupResponse>>;
|
||||
deleteGroupUser: (organizationId: string, id: string, organizationUserId: string) => Promise<any>;
|
||||
|
||||
getOrganizationUser: (
|
||||
|
||||
@@ -161,8 +161,8 @@ import { TwoFactorEmailResponse } from "../models/response/twoFactorEmailRespons
|
||||
import { TwoFactorProviderResponse } from "../models/response/twoFactorProviderResponse";
|
||||
import { TwoFactorRecoverResponse } from "../models/response/twoFactorRescoverResponse";
|
||||
import {
|
||||
TwoFactorWebAuthnResponse,
|
||||
ChallengeResponse,
|
||||
TwoFactorWebAuthnResponse,
|
||||
} from "../models/response/twoFactorWebAuthnResponse";
|
||||
import { TwoFactorYubiKeyResponse } from "../models/response/twoFactorYubiKeyResponse";
|
||||
import { UserKeyResponse } from "../models/response/userKeyResponse";
|
||||
@@ -916,7 +916,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return new GroupDetailsResponse(r);
|
||||
}
|
||||
|
||||
async getGroups(organizationId: string): Promise<ListResponse<GroupResponse>> {
|
||||
async getGroups(organizationId: string): Promise<ListResponse<GroupDetailsResponse>> {
|
||||
const r = await this.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/groups",
|
||||
@@ -924,7 +924,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new ListResponse(r, GroupResponse);
|
||||
return new ListResponse(r, GroupDetailsResponse);
|
||||
}
|
||||
|
||||
async getGroupUsers(organizationId: string, id: string): Promise<string[]> {
|
||||
@@ -984,6 +984,20 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async deleteManyGroups(
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRequest
|
||||
): Promise<ListResponse<GroupResponse>> {
|
||||
const r = await this.send(
|
||||
"DELETE",
|
||||
"/organizations/" + organizationId + "/groups",
|
||||
request,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new ListResponse(r, GroupResponse);
|
||||
}
|
||||
|
||||
deleteGroupUser(organizationId: string, id: string, organizationUserId: string): Promise<any> {
|
||||
return this.send(
|
||||
"DELETE",
|
||||
|
||||
@@ -9,5 +9,6 @@ export * from "./dialog";
|
||||
export * from "./submit-button";
|
||||
export * from "./link";
|
||||
export * from "./tabs";
|
||||
export * from "./table";
|
||||
export * from "./toggle-group";
|
||||
export * from "./utils/i18n-mock.service";
|
||||
|
||||
Reference in New Issue
Block a user