mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
[PM-20644] [Vault] [Browser Extension] Front End Changes to Enforce "Remove card item type policy" (#15147)
* Added service to get restricted cipher and used that to hide in autofill settings * Referenced files from the work done on web * Fixed restrictedCardType$ observable * Created resuseable cipher menu items type (cherry picked from commit 34be7f7ffef135aea2449e11e45e638ebaf34ee8) * Updated new item dropdown to filter out restricted type and also render the menu items dynamically (cherry picked from commit 566099ba9f3dbd7f18077dbc5b8ed44f51a94bfc) * Updated service to have cipher types as an observable (cherry picked from commit 6848e5f75803eb45e2262c617c9805359861ad14) * Refactored service to have use CIPHER MENU ITEMS type and filter restricted rypes and return an observable (cherry picked from commit e25c4eb18af895deac762b9e2d7ae69cc235f224) * Fixed type enum * Referenced files from the work done on web * Referenced change from the work done on web * Remove comment * Remove cipher type from autofill suggestion list when enabled * revert autofillcipher$ change * Fixed test * Added sharereplay to restrictedCardType$ observable * Added startwith operator * Add organization exemptions to restricted filter
This commit is contained in:
@@ -65,7 +65,10 @@
|
||||
{{ "showInlineMenuIdentitiesLabel" | i18n }}
|
||||
</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control *ngIf="enableInlineMenu" class="tw-ml-5">
|
||||
<bit-form-control
|
||||
*ngIf="enableInlineMenu && !(restrictedCardType$ | async)"
|
||||
class="tw-ml-5"
|
||||
>
|
||||
<input
|
||||
bitCheckbox
|
||||
id="show-inline-menu-cards"
|
||||
@@ -114,7 +117,7 @@
|
||||
</a>
|
||||
</bit-hint>
|
||||
</bit-form-control>
|
||||
<bit-form-control>
|
||||
<bit-form-control *ngIf="!(restrictedCardType$ | async)">
|
||||
<input
|
||||
bitCheckbox
|
||||
id="showCardsSuggestions"
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ReactiveFormsModule,
|
||||
} from "@angular/forms";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { filter, firstValueFrom, Observable, switchMap } from "rxjs";
|
||||
import { filter, firstValueFrom, map, Observable, shareReplay, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
@@ -44,6 +44,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import {
|
||||
CardComponent,
|
||||
CheckboxModule,
|
||||
@@ -57,6 +58,7 @@ import {
|
||||
SelectModule,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/vault";
|
||||
|
||||
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
@@ -111,6 +113,11 @@ export class AutofillComponent implements OnInit {
|
||||
this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id),
|
||||
),
|
||||
);
|
||||
protected restrictedCardType$: Observable<boolean> =
|
||||
this.restrictedItemTypesService.restricted$.pipe(
|
||||
map((restrictedTypes) => restrictedTypes.some((type) => type.cipherType === CipherType.Card)),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
||||
protected autofillOnPageLoadForm = new FormGroup({
|
||||
autofillOnPageLoad: new FormControl(),
|
||||
@@ -156,6 +163,7 @@ export class AutofillComponent implements OnInit {
|
||||
private nudgesService: NudgesService,
|
||||
private accountService: AccountService,
|
||||
private autofillBrowserSettingsService: AutofillBrowserSettingsService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
this.autofillOnPageLoadOptions = [
|
||||
{ name: this.i18nService.t("autoFillOnPageLoadYes"), value: true },
|
||||
|
||||
@@ -3,34 +3,12 @@
|
||||
{{ "new" | i18n }}
|
||||
</button>
|
||||
<bit-menu #itemOptions>
|
||||
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Login)">
|
||||
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
|
||||
{{ "typeLogin" | i18n }}
|
||||
</a>
|
||||
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Card)">
|
||||
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
|
||||
{{ "typeCard" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
bitMenuItem
|
||||
[routerLink]="['/add-cipher']"
|
||||
[queryParams]="buildQueryParams(cipherType.Identity)"
|
||||
>
|
||||
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
|
||||
{{ "typeIdentity" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
bitMenuItem
|
||||
[routerLink]="['/add-cipher']"
|
||||
[queryParams]="buildQueryParams(cipherType.SecureNote)"
|
||||
>
|
||||
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
|
||||
{{ "note" | i18n }}
|
||||
</a>
|
||||
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.SshKey)">
|
||||
<i class="bwi bwi-key" slot="start" aria-hidden="true"></i>
|
||||
{{ "typeSshKey" | i18n }}
|
||||
</a>
|
||||
@for (menuItem of cipherMenuItems$ | async; track menuItem.type) {
|
||||
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(menuItem.type)">
|
||||
<i [class]="`bwi ${menuItem.icon}`" slot="start" aria-hidden="true"></i>
|
||||
{{ menuItem.labelKey | i18n }}
|
||||
</a>
|
||||
}
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<button type="button" bitMenuItem (click)="openFolderDialog()">
|
||||
<i class="bwi bwi-folder" slot="start" aria-hidden="true"></i>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CommonModule } from "@angular/common";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute, RouterLink } from "@angular/router";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -12,6 +13,7 @@ import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstraction
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components";
|
||||
import { RestrictedCipherType, RestrictedItemTypesService } from "@bitwarden/vault";
|
||||
|
||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
@@ -23,6 +25,7 @@ describe("NewItemDropdownV2Component", () => {
|
||||
let fixture: ComponentFixture<NewItemDropdownV2Component>;
|
||||
let dialogServiceMock: jest.Mocked<DialogService>;
|
||||
let browserApiMock: jest.Mocked<typeof BrowserApi>;
|
||||
let restrictedItemTypesServiceMock: jest.Mocked<RestrictedItemTypesService>;
|
||||
|
||||
const mockTab = { url: "https://example.com" };
|
||||
|
||||
@@ -44,6 +47,9 @@ describe("NewItemDropdownV2Component", () => {
|
||||
const folderServiceMock = mock<FolderService>();
|
||||
const folderApiServiceAbstractionMock = mock<FolderApiServiceAbstraction>();
|
||||
const accountServiceMock = mock<AccountService>();
|
||||
restrictedItemTypesServiceMock = {
|
||||
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
|
||||
} as any;
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -65,6 +71,7 @@ describe("NewItemDropdownV2Component", () => {
|
||||
{ provide: FolderService, useValue: folderServiceMock },
|
||||
{ provide: FolderApiServiceAbstraction, useValue: folderApiServiceAbstractionMock },
|
||||
{ provide: AccountService, useValue: accountServiceMock },
|
||||
{ provide: RestrictedItemTypesService, useValue: restrictedItemTypesServiceMock },
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { RouterLink } from "@angular/router";
|
||||
import { map, Observable } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherMenuItem, CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items";
|
||||
import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components";
|
||||
import { AddEditFolderDialogComponent } from "@bitwarden/vault";
|
||||
import { AddEditFolderDialogComponent, RestrictedItemTypesService } from "@bitwarden/vault";
|
||||
|
||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
@@ -34,7 +36,22 @@ export class NewItemDropdownV2Component implements OnInit {
|
||||
@Input()
|
||||
initialValues: NewItemInitialValues;
|
||||
|
||||
constructor(private dialogService: DialogService) {}
|
||||
/**
|
||||
* Observable of cipher menu items that are not restricted by policy
|
||||
*/
|
||||
readonly cipherMenuItems$: Observable<CipherMenuItem[]> =
|
||||
this.restrictedItemTypeService.restricted$.pipe(
|
||||
map((restrictedTypes) => {
|
||||
const restrictedTypeArr = restrictedTypes.map((item) => item.cipherType);
|
||||
|
||||
return CIPHER_MENU_ITEMS.filter((menuItem) => !restrictedTypeArr.includes(menuItem.type));
|
||||
}),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private dialogService: DialogService,
|
||||
private restrictedItemTypeService: RestrictedItemTypesService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.tab = await BrowserApi.getTabFromCurrentWindow();
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
fullWidth
|
||||
placeholderIcon="bwi-list"
|
||||
[placeholderText]="'type' | i18n"
|
||||
[options]="cipherTypes"
|
||||
[options]="cipherTypes$ | async"
|
||||
>
|
||||
</bit-chip-select>
|
||||
</form>
|
||||
|
||||
@@ -18,7 +18,7 @@ export class VaultListFiltersComponent {
|
||||
protected organizations$ = this.vaultPopupListFiltersService.organizations$;
|
||||
protected collections$ = this.vaultPopupListFiltersService.collections$;
|
||||
protected folders$ = this.vaultPopupListFiltersService.folders$;
|
||||
protected cipherTypes = this.vaultPopupListFiltersService.cipherTypes;
|
||||
protected cipherTypes$ = this.vaultPopupListFiltersService.cipherTypes$;
|
||||
|
||||
// Combine all filters into a single observable to eliminate the filters from loading separately in the UI.
|
||||
protected allFilters$ = combineLatest([
|
||||
|
||||
@@ -20,6 +20,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { RestrictedCipherType, RestrictedItemTypesService } from "@bitwarden/vault";
|
||||
|
||||
import {
|
||||
CachedFilterState,
|
||||
@@ -70,6 +71,10 @@ describe("VaultPopupListFiltersService", () => {
|
||||
const state$ = new BehaviorSubject<boolean>(false);
|
||||
const update = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
const restrictedItemTypesService = {
|
||||
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
_memberOrganizations$ = new BehaviorSubject<Organization[]>([]); // Fresh instance per test
|
||||
folderViews$ = new BehaviorSubject([]); // Fresh instance per test
|
||||
@@ -125,21 +130,46 @@ describe("VaultPopupListFiltersService", () => {
|
||||
provide: ViewCacheService,
|
||||
useValue: viewCacheService,
|
||||
},
|
||||
{
|
||||
provide: RestrictedItemTypesService,
|
||||
useValue: restrictedItemTypesService,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.inject(VaultPopupListFiltersService);
|
||||
});
|
||||
|
||||
describe("cipherTypes", () => {
|
||||
it("returns all cipher types", () => {
|
||||
expect(service.cipherTypes.map((c) => c.value)).toEqual([
|
||||
CipherType.Login,
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
CipherType.SecureNote,
|
||||
CipherType.SshKey,
|
||||
describe("cipherTypes$", () => {
|
||||
it("returns all cipher types when no restrictions", (done) => {
|
||||
restrictedItemTypesService.restricted$.next([]);
|
||||
|
||||
service.cipherTypes$.subscribe((cipherTypes) => {
|
||||
expect(cipherTypes.map((c) => c.value)).toEqual([
|
||||
CipherType.Login,
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
CipherType.SecureNote,
|
||||
CipherType.SshKey,
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("filters out restricted cipher types", (done) => {
|
||||
restrictedItemTypesService.restricted$.next([
|
||||
{ cipherType: CipherType.Card, allowViewOrgIds: [] },
|
||||
]);
|
||||
|
||||
service.cipherTypes$.subscribe((cipherTypes) => {
|
||||
expect(cipherTypes.map((c) => c.value)).toEqual([
|
||||
CipherType.Login,
|
||||
CipherType.Identity,
|
||||
CipherType.SecureNote,
|
||||
CipherType.SshKey,
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -452,6 +482,10 @@ describe("VaultPopupListFiltersService", () => {
|
||||
{ type: CipherType.SecureNote, collectionIds: [], organizationId: null },
|
||||
] as CipherView[];
|
||||
|
||||
beforeEach(() => {
|
||||
restrictedItemTypesService.restricted$.next([]);
|
||||
});
|
||||
|
||||
it("filters by cipherType", (done) => {
|
||||
service.filterFunction$.subscribe((filterFunction) => {
|
||||
expect(filterFunction(ciphers)).toEqual([ciphers[0]]);
|
||||
@@ -690,6 +724,9 @@ function createSeededVaultPopupListFiltersService(
|
||||
} as any;
|
||||
|
||||
const accountServiceMock = mockAccountServiceWith("userId" as UserId);
|
||||
const restrictedItemTypesServiceMock = {
|
||||
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
|
||||
} as any;
|
||||
const formBuilderInstance = new FormBuilder();
|
||||
|
||||
const seededCachedSignal = createMockSignal<CachedFilterState>(cachedState);
|
||||
@@ -713,6 +750,7 @@ function createSeededVaultPopupListFiltersService(
|
||||
stateProviderMock,
|
||||
accountServiceMock,
|
||||
viewCacheServiceMock,
|
||||
restrictedItemTypesServiceMock,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items";
|
||||
import { ChipSelectOption } from "@bitwarden/components";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/vault";
|
||||
|
||||
const FILTER_VISIBILITY_KEY = new KeyDefinition<boolean>(VAULT_SETTINGS_DISK, "filterVisibility", {
|
||||
deserializer: (obj) => obj,
|
||||
@@ -178,6 +180,7 @@ export class VaultPopupListFiltersService {
|
||||
private stateProvider: StateProvider,
|
||||
private accountService: AccountService,
|
||||
private viewCacheService: ViewCacheService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
this.filterForm.controls.organization.valueChanges
|
||||
.pipe(takeUntilDestroyed())
|
||||
@@ -210,74 +213,80 @@ export class VaultPopupListFiltersService {
|
||||
/**
|
||||
* Observable whose value is a function that filters an array of `CipherView` objects based on the current filters
|
||||
*/
|
||||
filterFunction$: Observable<(ciphers: CipherView[]) => CipherView[]> = this.filters$.pipe(
|
||||
filterFunction$: Observable<(ciphers: CipherView[]) => CipherView[]> = combineLatest([
|
||||
this.filters$,
|
||||
this.restrictedItemTypesService.restricted$.pipe(startWith([])),
|
||||
]).pipe(
|
||||
map(
|
||||
(filters) => (ciphers: CipherView[]) =>
|
||||
ciphers.filter((cipher) => {
|
||||
// Vault popup lists never shows deleted ciphers
|
||||
if (cipher.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.cipherType !== null && cipher.type !== filters.cipherType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.folder && cipher.folderId !== filters.folder.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isMyVault = filters.organization?.id === MY_VAULT_ID;
|
||||
|
||||
if (isMyVault) {
|
||||
if (cipher.organizationId !== null) {
|
||||
([filters, restrictions]) =>
|
||||
(ciphers: CipherView[]) =>
|
||||
ciphers.filter((cipher) => {
|
||||
// Vault popup lists never shows deleted ciphers
|
||||
if (cipher.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
} else if (filters.organization) {
|
||||
if (cipher.organizationId !== filters.organization.id) {
|
||||
|
||||
// Check if cipher type is restricted (with organization exemptions)
|
||||
if (restrictions && restrictions.length > 0) {
|
||||
const isRestricted = restrictions.some(
|
||||
(restrictedType) =>
|
||||
restrictedType.cipherType === cipher.type &&
|
||||
(cipher.organizationId
|
||||
? !restrictedType.allowViewOrgIds.includes(cipher.organizationId)
|
||||
: restrictedType.allowViewOrgIds.length === 0),
|
||||
);
|
||||
|
||||
if (isRestricted) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.cipherType !== null && cipher.type !== filters.cipherType) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.folder && cipher.folderId !== filters.folder.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isMyVault = filters.organization?.id === MY_VAULT_ID;
|
||||
|
||||
if (isMyVault) {
|
||||
if (cipher.organizationId !== null) {
|
||||
return false;
|
||||
}
|
||||
} else if (filters.organization) {
|
||||
if (cipher.organizationId !== filters.organization.id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* All available cipher types
|
||||
* All available cipher types (filtered by policy restrictions)
|
||||
*/
|
||||
readonly cipherTypes: ChipSelectOption<CipherType>[] = [
|
||||
{
|
||||
value: CipherType.Login,
|
||||
label: this.i18nService.t("typeLogin"),
|
||||
icon: "bwi-globe",
|
||||
},
|
||||
{
|
||||
value: CipherType.Card,
|
||||
label: this.i18nService.t("typeCard"),
|
||||
icon: "bwi-credit-card",
|
||||
},
|
||||
{
|
||||
value: CipherType.Identity,
|
||||
label: this.i18nService.t("typeIdentity"),
|
||||
icon: "bwi-id-card",
|
||||
},
|
||||
{
|
||||
value: CipherType.SecureNote,
|
||||
label: this.i18nService.t("note"),
|
||||
icon: "bwi-sticky-note",
|
||||
},
|
||||
{
|
||||
value: CipherType.SshKey,
|
||||
label: this.i18nService.t("typeSshKey"),
|
||||
icon: "bwi-key",
|
||||
},
|
||||
];
|
||||
readonly cipherTypes$: Observable<ChipSelectOption<CipherType>[]> =
|
||||
this.restrictedItemTypesService.restricted$.pipe(
|
||||
map((restrictedTypes) => {
|
||||
const restrictedCipherTypes = restrictedTypes.map((r) => r.cipherType);
|
||||
|
||||
return CIPHER_MENU_ITEMS.filter((item) => !restrictedCipherTypes.includes(item.type)).map(
|
||||
(item) => ({
|
||||
value: item.type,
|
||||
label: this.i18nService.t(item.labelKey),
|
||||
icon: item.icon,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
/** Resets `filterForm` to the original state */
|
||||
resetFilterForm(): void {
|
||||
|
||||
24
libs/common/src/vault/types/cipher-menu-items.ts
Normal file
24
libs/common/src/vault/types/cipher-menu-items.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { CipherType } from "../enums";
|
||||
|
||||
/**
|
||||
* Represents a menu item for creating a new cipher of a specific type
|
||||
*/
|
||||
export type CipherMenuItem = {
|
||||
/** The cipher type this menu item represents */
|
||||
type: CipherType;
|
||||
/** The icon class name (e.g., "bwi-globe") */
|
||||
icon: string;
|
||||
/** The i18n key for the label text */
|
||||
labelKey: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* All available cipher menu items with their associated icons and labels
|
||||
*/
|
||||
export const CIPHER_MENU_ITEMS = Object.freeze([
|
||||
{ type: CipherType.Login, icon: "bwi-globe", labelKey: "typeLogin" },
|
||||
{ type: CipherType.Card, icon: "bwi-credit-card", labelKey: "typeCard" },
|
||||
{ type: CipherType.Identity, icon: "bwi-id-card", labelKey: "typeIdentity" },
|
||||
{ type: CipherType.SecureNote, icon: "bwi-sticky-note", labelKey: "note" },
|
||||
{ type: CipherType.SshKey, icon: "bwi-key", labelKey: "typeSshKey" },
|
||||
] as const) satisfies readonly CipherMenuItem[];
|
||||
Reference in New Issue
Block a user