domain rules page implementation
This commit is contained in:
parent
63ccc49ce2
commit
998a63612f
|
@ -2,19 +2,64 @@
|
||||||
<h1>{{'domainRules' | i18n}}</h1>
|
<h1>{{'domainRules' | i18n}}</h1>
|
||||||
</div>
|
</div>
|
||||||
<p>{{'domainRulesDesc' | i18n}}</p>
|
<p>{{'domainRulesDesc' | i18n}}</p>
|
||||||
<h2>{{'customEqDomains' | i18n}}</h2>
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="loading"></i>
|
<h2>{{'customEqDomains' | i18n}}</h2>
|
||||||
<table class="table table-hover table-list" *ngIf="!loading && custom.length > 0">
|
<p *ngIf="loading">
|
||||||
<tbody>
|
<i class="fa fa-spinner fa-spin text-muted"></i>
|
||||||
<tr *ngFor="let d of custom">
|
</p>
|
||||||
</tr>
|
<ng-container *ngIf="!loading">
|
||||||
</tbody>
|
<div class="form-group d-flex" *ngFor="let d of custom; let i = index; trackBy: indexTrackBy">
|
||||||
</table>
|
<div class="flex-fill">
|
||||||
<h2>{{'globalEqDomains' | i18n}}</h2>
|
<label for="customDomain_{{i}}" class="sr-only">{{'customDomainX' | i18n : (i + 1)}}</label>
|
||||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="loading"></i>
|
<textarea class="form-control" name="CustomDomain[{{i}}]" id="customDomain_{{i}}" [(ngModel)]="custom[i]" placeholder="{{'ex' | i18n}} google.com, gmail.com"></textarea>
|
||||||
<table class="table table-hover table-list" *ngIf="!loading && global.length > 0">
|
</div>
|
||||||
<tbody>
|
<button type="button" class="btn btn-link text-danger ml-2" (click)="remove(i)" title="{{'remove' | i18n}}">
|
||||||
<tr *ngFor="let d of global">
|
<i class="fa fa-minus-circle fa-lg"></i>
|
||||||
</tr>
|
</button>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
<button type="button" appBlurClick (click)="add()" class="btn btn-outline-secondary btn-sm mb-2">
|
||||||
|
<i class="fa fa-plus-circle fa-fw"></i> {{'newCustomDomain' | i18n}}
|
||||||
|
</button>
|
||||||
|
<small class="text-muted d-block mb-3">{{'newCustomDomainDesc' | i18n}}</small>
|
||||||
|
</ng-container>
|
||||||
|
<button appBlurClick type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
<span>{{'save' | i18n}}</span>
|
||||||
|
</button>
|
||||||
|
<h2 class="mt-5">{{'globalEqDomains' | i18n}}</h2>
|
||||||
|
<p *ngIf="loading">
|
||||||
|
<i class="fa fa-spinner fa-spin text-muted"></i>
|
||||||
|
</p>
|
||||||
|
<table class="table table-hover table-list" *ngIf="!loading && global.length > 0">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let d of global">
|
||||||
|
<td class="normal-lh" [ngClass]="{'table-list-strike': d.excluded}">{{d.domains}}</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">
|
||||||
|
<i class="fa fa-cog fa-lg"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="#" appStopClick (click)="toggleExcluded(d)" *ngIf="!d.excluded">
|
||||||
|
<i class="fa fa-fw fa-close"></i>
|
||||||
|
{{'exclude' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" appStopClick (click)="toggleExcluded(d)" *ngIf="d.excluded">
|
||||||
|
<i class="fa fa-fw fa-plus"></i>
|
||||||
|
{{'include' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" appStopClick (click)="customize(d)">
|
||||||
|
<i class="fa fa-fw fa-scissors"></i>
|
||||||
|
{{'customize' | i18n}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button appBlurClick type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
<span>{{'save' | i18n}}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
|
@ -7,9 +7,9 @@ import { ToasterService } from 'angular2-toaster';
|
||||||
import { Angulartics2 } from 'angulartics2';
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
|
||||||
|
import { UpdateDomainsRequest } from 'jslib/models/request/updateDomainsRequest';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-domain-rules',
|
selector: 'app-domain-rules',
|
||||||
|
@ -18,19 +18,68 @@ import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
export class DomainRulesComponent implements OnInit {
|
export class DomainRulesComponent implements OnInit {
|
||||||
loading = true;
|
loading = true;
|
||||||
custom: string[] = [];
|
custom: string[] = [];
|
||||||
global: string[] = [];
|
global: any[] = [];
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||||
private cryptoService: CryptoService, private messagingService: MessagingService) { }
|
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const response = await this.apiService.getSettingsDomains();
|
const response = await this.apiService.getSettingsDomains();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
if (response.equivalentDomains != null) {
|
||||||
|
this.custom = response.equivalentDomains.map((d) => d.join(', '));
|
||||||
|
}
|
||||||
|
if (response.globalEquivalentDomains != null) {
|
||||||
|
this.global = response.globalEquivalentDomains.map((d) => {
|
||||||
|
return {
|
||||||
|
domains: d.domains.join(', '),
|
||||||
|
excluded: d.excluded,
|
||||||
|
key: d.type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleExcluded(globalDomain: any) {
|
||||||
|
globalDomain.excluded = !globalDomain.excluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
customize(globalDomain: any) {
|
||||||
|
globalDomain.excluded = true;
|
||||||
|
this.custom.push(globalDomain.domains);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(index: number) {
|
||||||
|
this.custom.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
add() {
|
||||||
|
this.custom.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
|
const request = new UpdateDomainsRequest();
|
||||||
|
request.excludedGlobalEquivalentDomains = this.global.filter((d) => d.excluded)
|
||||||
|
.map((d) => d.key);
|
||||||
|
if (request.excludedGlobalEquivalentDomains.length === 0) {
|
||||||
|
request.excludedGlobalEquivalentDomains = null;
|
||||||
|
}
|
||||||
|
request.equivalentDomains = this.custom.filter((d) => d != null && d.trim() !== '')
|
||||||
|
.map((d) => d.split(' ').join('').split(','));
|
||||||
|
if (request.equivalentDomains.length === 0) {
|
||||||
|
request.equivalentDomains = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.formPromise = this.apiService.putSettingsDomains(request);
|
||||||
|
await this.formPromise;
|
||||||
|
this.analytics.eventTrack.next({ action: 'Saved Equivalent Domains' });
|
||||||
|
this.toasterService.popAsync('success', null, this.i18nService.t('domainsUpdated'));
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTrackBy(index: number, obj: any): any {
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@
|
||||||
<select class="form-control" id="loginUriMatch{{i}}" name="Login.Uris[{{i}}].Match" [(ngModel)]="u.match" (change)="loginUriMatchChanged(u)">
|
<select class="form-control" id="loginUriMatch{{i}}" name="Login.Uris[{{i}}].Match" [(ngModel)]="u.match" (change)="loginUriMatchChanged(u)">
|
||||||
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
|
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="btn btn-link text-danger ml-2" (click)="removeUri(u)" title="{{'remove' | i18n}}">
|
<button type="button" class="btn btn-link text-danger ml-2" (click)="removeUri(u)" title="{{'remove' | i18n}}">
|
||||||
<i class="fa fa-minus-circle fa-lg"></i>
|
<i class="fa fa-minus-circle fa-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -328,7 +328,7 @@
|
||||||
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox" [(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean"
|
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox" [(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean"
|
||||||
appTrueFalseValue trueValue="true" falseValue="false">
|
appTrueFalseValue trueValue="true" falseValue="false">
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-link text-danger ml-2" (click)="removeField(f)" title="{{'remove' | i18n}}">
|
<button type="button" class="btn btn-link text-danger ml-2" (click)="removeField(f)" title="{{'remove' | i18n}}">
|
||||||
<i class="fa fa-minus-circle fa-lg"></i>
|
<i class="fa fa-minus-circle fa-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -924,10 +924,37 @@
|
||||||
"domainRulesDesc": {
|
"domainRulesDesc": {
|
||||||
"message": "If you have the same login across multiple different website domains, you can mark the website as \"equivalent\". \"Global\" domains are ones already created for you by Bitwarden."
|
"message": "If you have the same login across multiple different website domains, you can mark the website as \"equivalent\". \"Global\" domains are ones already created for you by Bitwarden."
|
||||||
},
|
},
|
||||||
"globalEqRules": {
|
"globalEqDomains": {
|
||||||
"message": "Global Equivalent Domains"
|
"message": "Global Equivalent Domains"
|
||||||
},
|
},
|
||||||
"customEqRules": {
|
"customEqDomains": {
|
||||||
"message": "Custom Equivalent Domains"
|
"message": "Custom Equivalent Domains"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"message": "Exclude"
|
||||||
|
},
|
||||||
|
"include": {
|
||||||
|
"message": "Include"
|
||||||
|
},
|
||||||
|
"customize": {
|
||||||
|
"message": "Customize"
|
||||||
|
},
|
||||||
|
"newCustomDomain": {
|
||||||
|
"message": "New Custom Domain"
|
||||||
|
},
|
||||||
|
"newCustomDomainDesc": {
|
||||||
|
"message": "Only \"base\" domains are allowed. Do not enter subdomains. For example, enter \"google.com\" instead of \"www.google.com\". You can also enter \"androidapp://package.name\" to associate an android app with other website domains."
|
||||||
|
},
|
||||||
|
"customDomainX": {
|
||||||
|
"message": "Custom Domain $INDEX$",
|
||||||
|
"placeholders": {
|
||||||
|
"index": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domainsUpdated": {
|
||||||
|
"message": "Domains updated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ $navbar-dark-hover-color: rgba(#ffffff, .9);
|
||||||
$input-bg: #fbfbfb;
|
$input-bg: #fbfbfb;
|
||||||
$input-focus-bg: #ffffff;
|
$input-focus-bg: #ffffff;
|
||||||
$input-disabled-bg: #e0e0e0;
|
$input-disabled-bg: #e0e0e0;
|
||||||
|
$input-placeholder-color: #b4b4b4;
|
||||||
|
|
||||||
$table-accent-bg: rgba(#000000, .02);
|
$table-accent-bg: rgba(#000000, .02);
|
||||||
$table-hover-bg: rgba(#000000, .03);
|
$table-hover-bg: rgba(#000000, .03);
|
||||||
|
@ -229,7 +230,10 @@ label:not(.form-check-label) {
|
||||||
|
|
||||||
td {
|
td {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 1;
|
|
||||||
|
&:not(.normal-lh) {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
small, > .fa, .icon {
|
small, > .fa, .icon {
|
||||||
color: $text-muted;
|
color: $text-muted;
|
||||||
|
@ -273,6 +277,11 @@ label:not(.form-check-label) {
|
||||||
width: 35px;
|
width: 35px;
|
||||||
max-width: 35px;
|
max-width: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.table-list-strike {
|
||||||
|
color: $text-muted;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app-vault-groupings {
|
app-vault-groupings {
|
||||||
|
|
Loading…
Reference in New Issue