mirror of
https://github.com/bitwarden/browser
synced 2025-12-27 13:43:41 +00:00
SG-680 - Domain verification progress - (1) Table layout + loading working for the most part (more translations needed (2) Add & edit opening dialog (3) Dialog first draft of save and verify
This commit is contained in:
@@ -1,7 +1,20 @@
|
||||
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
|
||||
<span bitDialogTitle>{{ "newDomain" | i18n }}</span>
|
||||
<span bitDialogTitle>
|
||||
<span *ngIf="!data.orgDomain">{{ "newDomain" | i18n }}</span>
|
||||
<span *ngIf="data.orgDomain"> {{ "verifyDomain" | i18n }}</span>
|
||||
|
||||
<span *ngIf="data.orgDomain" class="tw-text-xs tw-text-muted">{{
|
||||
data.orgDomain.domainName
|
||||
}}</span>
|
||||
|
||||
<!-- TODO: get status badge here -->
|
||||
</span>
|
||||
<div bitDialogContent>
|
||||
<form [formGroup]="domainForm">
|
||||
<form [formGroup]="domainForm" [bitSubmit]="verifyDomain">
|
||||
<!-- <input bitInput formControlName="domainName" /> -->
|
||||
<!-- <span *ngIf="domainNameCtrl.touched && domainNameCtrl.invalid">Error</span> -->
|
||||
|
||||
<!-- TODO: investigate why bit-form-field breaks touched -->
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "domainName" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="domainName" />
|
||||
@@ -29,10 +42,17 @@
|
||||
</bit-callout>
|
||||
</div>
|
||||
<div bitDialogFooter class="tw-flex tw-flex-row tw-items-center tw-gap-2">
|
||||
<button bitButton buttonType="primary" [disabled]="domainForm.invalid">
|
||||
<button
|
||||
type="submit"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
[disabled]="domainForm.invalid"
|
||||
(click)="data.orgDomain ? verifyDomain() : saveDomain()"
|
||||
[loading]="submitting"
|
||||
>
|
||||
{{ "verifyDomain" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" (click)="dialogRef.close()">
|
||||
<button bitButton buttonType="secondary" [disabled]="submitting" (click)="dialogRef.close()">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
@@ -43,6 +63,9 @@
|
||||
size="default"
|
||||
title="Delete"
|
||||
aria-label="Delete"
|
||||
(click)="deleteDomain()"
|
||||
[disabled]="submitting"
|
||||
[loading]="deleting"
|
||||
></button>
|
||||
</div>
|
||||
</bit-dialog>
|
||||
|
||||
@@ -4,14 +4,17 @@ import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms"
|
||||
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/abstractions/organization-domain/org-domain-api.service.abstraction";
|
||||
import { OrganizationDomainResponse } from "@bitwarden/common/abstractions/organization-domain/responses/organization-domain.response";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { OrganizationDomainRequest } from "@bitwarden/common/services/organization-domain/requests/organization-domain.request";
|
||||
|
||||
import { domainNameValidator } from "./domain-name.validator";
|
||||
export interface DomainAddEditDialogData {
|
||||
organizationId: string;
|
||||
orgDomain: OrganizationDomainResponse;
|
||||
existingDomainNames: Array<string>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -22,6 +25,8 @@ export class DomainAddEditDialogComponent implements OnInit {
|
||||
dialogSize: "small" | "default" | "large" = "default";
|
||||
disablePadding = false;
|
||||
|
||||
// TODO: should invalidDomainNameMessage have something like: "'https://', 'http://', or 'www.' domain prefixes not allowed."
|
||||
// TODO: write separate uniqueIn validator w/ translated msg: "You can’t claim the same domain twice."
|
||||
domainForm: FormGroup = this.formBuilder.group({
|
||||
domainName: [
|
||||
"",
|
||||
@@ -37,13 +42,17 @@ export class DomainAddEditDialogComponent implements OnInit {
|
||||
return this.domainForm.controls.txt as FormControl;
|
||||
}
|
||||
|
||||
submitting = false;
|
||||
deleting = false;
|
||||
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) public data: DomainAddEditDialogData,
|
||||
private formBuilder: FormBuilder,
|
||||
private cryptoFunctionService: CryptoFunctionServiceAbstraction,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService
|
||||
private i18nService: I18nService,
|
||||
private orgDomainApiService: OrgDomainApiServiceAbstraction
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@@ -55,6 +64,7 @@ export class DomainAddEditDialogComponent implements OnInit {
|
||||
if (this.data.orgDomain) {
|
||||
// Edit
|
||||
this.domainForm.patchValue(this.data.orgDomain);
|
||||
this.domainForm.disable();
|
||||
} else {
|
||||
// Add
|
||||
|
||||
@@ -69,12 +79,78 @@ export class DomainAddEditDialogComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
copyDnsTxt() {
|
||||
copyDnsTxt(): void {
|
||||
this.platformUtilsService.copyToClipboard(this.txtCtrl.value);
|
||||
this.platformUtilsService.showToast(
|
||||
"info",
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t("dnsTxtRecord"))
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: error handling?
|
||||
|
||||
// TODO: probably will be a need to split into different actions: save == save + verify
|
||||
// and if edit true, then verify is verify.
|
||||
|
||||
// Need to display verified status somewhere
|
||||
// If verified, no action can be taken but delete
|
||||
// If saved & unverified, can prompt verification
|
||||
|
||||
async saveDomain(): Promise<void> {
|
||||
this.submitting = true;
|
||||
this.domainForm.disable();
|
||||
|
||||
const request: OrganizationDomainRequest = new OrganizationDomainRequest(
|
||||
this.txtCtrl.value,
|
||||
this.domainNameCtrl.value
|
||||
);
|
||||
|
||||
await this.orgDomainApiService.post(this.data.organizationId, request);
|
||||
|
||||
//TODO: figure out how to handle DomainVerifiedException
|
||||
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("domainSaved"));
|
||||
this.submitting = false;
|
||||
|
||||
// TODO: verify before closing modal; close if successful
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
async verifyDomain(): Promise<void> {
|
||||
this.submitting = true;
|
||||
this.domainForm.disable();
|
||||
|
||||
const success: boolean = await this.orgDomainApiService.verify(
|
||||
this.data.organizationId,
|
||||
this.data.orgDomain.id
|
||||
);
|
||||
|
||||
this.submitting = false;
|
||||
|
||||
if (success) {
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("domainVerified"));
|
||||
this.dialogRef.close();
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("domainNotVerified", this.domainNameCtrl.value)
|
||||
);
|
||||
|
||||
// TODO: discuss with Danielle / Gbubemi:
|
||||
// Someone else is using [domain]. Use a different domain to continue.
|
||||
// I only have a bool to indicate success or failure.. not why it failed.
|
||||
}
|
||||
}
|
||||
|
||||
async deleteDomain(): Promise<void> {
|
||||
// TODO: Do I need an are you sure prompt?
|
||||
|
||||
this.deleting = true;
|
||||
await this.orgDomainApiService.delete(this.data.organizationId, this.data.orgDomain.id);
|
||||
this.deleting = false;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("domainRemoved"));
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,52 +15,85 @@
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
|
||||
<!-- <div *ngIf="orgDomains$ | async as orgDomains; else loading">
|
||||
{{ obs }}
|
||||
</div>
|
||||
<ng-template #loading>Loading...</ng-template> -->
|
||||
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="tw-flex tw-flex-row">
|
||||
<bit-table class="tw-w-full tw-table-auto">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>Name</th>
|
||||
<th bitCell>Status</th>
|
||||
<th bitCell>Last Checked</th>
|
||||
<th bitCell>Options</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-container body>
|
||||
<!-- [alignContent]="alignRowContent" -->
|
||||
<tr bitRow *ngFor="let orgDomain of orgDomains$ | async; index as i">
|
||||
<td bitCell>{{ orgDomain.domainName }}</td>
|
||||
<td bitCell>
|
||||
{{ orgDomain.domainName }}
|
||||
</td>
|
||||
<td bitCell>test</td>
|
||||
<td bitCell>test</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</bit-table>
|
||||
</div>
|
||||
<ng-container *ngIf="orgDomains$ | async as orgDomains">
|
||||
<div class="tw-flex tw-flex-row">
|
||||
<bit-table class="tw-w-full tw-table-auto">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>Name</th>
|
||||
<th bitCell>Status</th>
|
||||
<th bitCell>Last Checked</th>
|
||||
<th bitCell>Options</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-container body>
|
||||
<!-- [alignContent]="alignRowContent" -->
|
||||
<tr bitRow *ngFor="let orgDomain of orgDomains; index as i">
|
||||
<td bitCell>
|
||||
<a bitLink href appStopClick linkType="primary" (click)="editDomain(orgDomain)">{{
|
||||
orgDomain.domainName
|
||||
}}</a>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<span *ngIf="!orgDomain?.verifiedDate" bitBadge badgeType="warning">Unverified</span>
|
||||
<span *ngIf="orgDomain?.verifiedDate" bitBadge badgeType="success">Verified</span>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<!-- TODO: next run date != last checked... -->
|
||||
{{ orgDomain.nextRunDate | date: "medium" }}
|
||||
</td>
|
||||
|
||||
<div class="tw-mt-6 tw-flex tw-flex-col tw-items-center tw-justify-center">
|
||||
<img src="../../images/domain-verification/domain.svg" class="tw-mb-4" alt="domain" />
|
||||
|
||||
<div class="tw-mb-2 tw-flex tw-flex-row tw-justify-center">
|
||||
<span class="tw-text-lg tw-font-bold">{{ "noDomains" | i18n }}</span>
|
||||
<td bitCell class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="orgDomainOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #orgDomainOptions>
|
||||
<button bitMenuItem>
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
Copy DNS TXT record
|
||||
</button>
|
||||
<button bitMenuItem>
|
||||
<i class="bwi bwi-fw bwi-check text-success" aria-hidden="true"></i>
|
||||
Verify domain
|
||||
</button>
|
||||
<button bitMenuItem>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</bit-table>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-4 tw-flex tw-flex-row tw-justify-center">
|
||||
<span>
|
||||
{{ "noDomainsSubText" | i18n }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="tw-mt-6 tw-flex tw-flex-col tw-items-center tw-justify-center"
|
||||
*ngIf="orgDomains?.length == 0"
|
||||
>
|
||||
<img src="../../images/domain-verification/domain.svg" class="tw-mb-4" alt="domain" />
|
||||
|
||||
<button type="button" buttonType="secondary" bitButton (click)="addDomain()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i> {{ "newDomain" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
¸
|
||||
<div class="tw-mb-2 tw-flex tw-flex-row tw-justify-center">
|
||||
<span class="tw-text-lg tw-font-bold">{{ "noDomains" | i18n }}</span>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-4 tw-flex tw-flex-row tw-justify-center">
|
||||
<span>
|
||||
{{ "noDomainsSubText" | i18n }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button type="button" buttonType="secondary" bitButton (click)="addDomain()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i> {{ "newDomain" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
@@ -68,6 +68,19 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
|
||||
const domainAddEditDialogData: DomainAddEditDialogData = {
|
||||
organizationId: this.organizationId,
|
||||
orgDomain: null,
|
||||
existingDomainNames: [],
|
||||
};
|
||||
|
||||
this.dialogService.open(DomainAddEditDialogComponent, {
|
||||
data: domainAddEditDialogData,
|
||||
});
|
||||
}
|
||||
|
||||
editDomain(orgDomain: OrganizationDomainResponse) {
|
||||
const domainAddEditDialogData: DomainAddEditDialogData = {
|
||||
organizationId: this.organizationId,
|
||||
orgDomain: orgDomain,
|
||||
existingDomainNames: [],
|
||||
};
|
||||
|
||||
this.dialogService.open(DomainAddEditDialogComponent, {
|
||||
|
||||
Reference in New Issue
Block a user