Archive commit

This commit is contained in:
Erik McClure
2019-08-28 21:15:02 -07:00
commit 735845746e
1435 changed files with 140724 additions and 0 deletions
@@ -0,0 +1,447 @@
div(*ngIf="account")
.row
.col-md-12
h2
| {{account.name}}
small.text-muted.ml-1
em {{account.name | translit}}
.row
.col-md-4
.mb-2
account-info([account]="account" popoverPlacement="right")
.mb-2
code.text-muted {{account._id}}
.mb-2
div
span.text-muted created:
from-now.ml-1([time]="account.createdAt")
span.text-muted.ml-1 ({{account.createdAt | date}})
div
span.text-muted last visit:
from-now.ml-1([time]="account.lastVisit")
span.text-muted.ml-1 ({{account.lastVisit | date}})
div
span.text-muted last browser:
ua-info.ml-1([userAgent]="accountObject?.lastUserAgent")
div
span.text-muted age:
span([class.text-strike]="account.birthyear")
span.ml-1 {{age}}
span.text-muted.ml-1 ({{account.birthdate | date:'yyyy-MM-dd'}})
span(*ngIf="account.birthyear")
span.ml-1 {{forceAge}}
span.text-muted.ml-1 ({{account.birthyear}})
.d-inline-block.dropdown(dropdown)
a.icon-button.ml-1(dropdownToggle)
fa-icon([icon]="cogIcon")
.dropdown-menu(*dropdownMenu)
button.dropdown-item((click)="setAge(-1)")
| unset
button.dropdown-item(*ngFor="let age of ages" (click)="setAge(age)")
| set to #[b {{age}}]yo
.d-flex.justify-content-between.align-items-start
h5 Counters
div
button.btn.btn-xs.btn-default.ml-1((click)="refreshDetails()" title="Refresh account details")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
.mb-2
div(*ngFor="let counter of counters")
span.text-muted {{counter.key}}:
span.ml-1 {{counter.value}}
.d-flex.justify-content-between.align-items-start
h5 Status
div
button.btn.btn-xs.btn-default.ml-1((click)="clearSessions()" title="Sign out user" [disabled]="clearingSessions")
fa-icon([icon]="clearingSessions ? spinnerIcon : signOutIcon" [fixedWidth]="true" [spin]="clearingSessions")
button.btn.btn-xs.btn-default.ml-1((click)="showAccountData()" title="Fetch account data (in console)")
fa-icon([icon]="consoleIcon" [fixedWidth]="true")
button.btn.btn-xs.btn-default.ml-1((click)="accountStatus.refresh()" title="Refresh status")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
.mb-2
account-status(#accountStatus [account]="account" [verbose]="true")
h5 Roles
.mb-2
.btn-group.btn-group-sm
button.btn(*ngFor="let r of roles" [btnHighlight]="hasRole(r)" (click)="toggleRole(r)" [disabled]="!canToggleRole(r)")
| {{r}}
button.btn.btn-sm.btn-danger.ml-1((click)="remove()" [disabled]="!canRemove" title="Delete account")
fa-icon([icon]="trashIcon" [fixedWidth]="true")
.d-flex.justify-content-between.align-items-start
h5
| Notes
a.text-muted.ml-1([href]="translateUrl(account.note)" target="_blank" title="Translate note")
fa-icon([icon]="langIcon")
.text-muted
from-now([time]="account.noteUpdated")
.well.pre-line.text-truncate
| {{account.note}}
.d-flex.justify-content-between.align-items-start
h5 Merges #[span.text-muted ({{merges?.length || 0}})]
div
button.btn.btn-xs.btn-default.ml-1((click)="refreshDetails()" title="Refresh account details")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
table.table.table-sm(*ngIf="merges?.length")
thead
tr
th id
th name
th date
tbody
tr(*ngFor="let m of merges" [class.text-slashed]="m.split")
td
ng-template(#mergeTooltip)
div {{getMergeTooltip(m)}}
div([tooltip]="mergeTooltip" placement="right" containerClass="tooltip-merge")
| {{m.id}}
td {{m.name}}
td
.d-flex
.flex-grow-1
time-field([time]="m.date")
.dropdown.float-right(dropdown)
button.btn.btn-xs.btn-default.dropdown-toggle(
dropdownToggle [tooltip]="(m.reason || '[null reason]') + (m.split ? ' (split)' : '')")
fa-icon([icon]="cogIcon" [fixedWidth]="true")
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
button.dropdown-item((click)="printMerge(m)")
| Print merge info
button.dropdown-item((click)="printMerge2(m)")
| Print usable merge info
button.dropdown-item((click)="showMergeInNewTab(m)")
| Show merge info
button.dropdown-item((click)="showMergeInNewTab2(m)")
| Show usable merge info
.dropdown-divider
button.dropdown-item((click)="unmerge(m._id, m.data.merge, m.data.account)")
| Split off #[b merged]
button.dropdown-item((click)="unmerge(m._id, m.data.account, m.data.merge)")
| Split off #[b account]
h5 Support #[span.text-muted ({{support?.length || 0}})]
table.table.table-sm(*ngIf="support?.length")
thead
tr
th message
th date
tbody
tr(*ngFor="let l of support")
td
fa-icon.mr-1([icon]="l.icon" [ngClass]="l.class")
| {{l.message}}
td
time-field.mr-2([time]="l.date")
span.text-muted ({{l.date | date:'MMM d'}})
h5 Ban log #[span.text-muted ({{banLog?.length || 0}})]
table.table.table-sm(*ngIf="banLog?.length")
thead
tr
th message
th date
tbody
tr(*ngFor="let l of banLog")
td {{l.message}}
td: time-field([time]="l.date")
.col-md-5
//- auths
.d-flex.justify-content-between.align-items-start
h5 Auths #[span.text-muted ({{auths.length}})]
.dropdown(dropdown)
button.btn.btn-xs.btn-default.dropdown-toggle(dropdownToggle)
fa-icon([icon]="cogIcon" [fixedWidth]="true")
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
a.dropdown-item((click)="fetchAuths()")
| Fetch auths data
a.dropdown-item((click)="printAuthList()")
| Print auths list
a.dropdown-item((click)="printAuthData()")
| Print auths data
.mb-2
auth-info-edit(*ngFor="let a of auths | slice:0:authLimit" [authId]="a" [duplicates]="duplicates")
a.text-muted((click)="authLimit = 99999" *ngIf="authLimit < auths.length")
| more ...
.well.mt-2.mb-1.py-2(*ngIf="authData" style="font-size: 12px;")
button.close((click)="clearAuthData()" style="color: #ddd; margin-right: -5px; margin-top: -5px;") &times;
button.close((click)="copyAuthData()" style="color: #ddd; margin-right: 6px; margin-top: -5px;")
fa-icon([icon]="copyIcon" size="xs")
.text-muted Auths for {{authDataAccount}}
.pre-line {{authData}}
//- origins
.d-flex.justify-content-between
h5 Origins #[span.text-muted ({{origins.length}})]
div
button.btn.btn-xs.btn-default((click)="clearOrigins(true, true)" title="Remove all origins older than 14 days used by only one account")
fa-icon.mr-1([icon]="trashIcon")
| old single
button.btn.btn-xs.btn-default.ml-1((click)="clearOrigins(true, false)" title="Remove all origins older than 14 days")
fa-icon.mr-1([icon]="trashIcon")
| old
button.btn.btn-xs.btn-default.ml-1((click)="clearOrigins(false, true)" title="Remove all origins used by only one account")
fa-icon.mr-1([icon]="trashIcon")
| single
button.btn.btn-xs.btn-default.ml-1((click)="clearOrigins(false, false)" title="Remove all origins")
fa-icon.mr-1([icon]="trashIcon")
| all
.dropdown.d-inline-block.ml-1(dropdown)
button.btn.btn-xs.btn-default.dropdown-toggle(dropdownToggle title="Remove all origins in region")
fa-icon.mr-1([icon]="trashIcon")
| region
.dropdown-menu(*dropdownMenu)
a.dropdown-item(*ngFor="let region of originRegions" (click)="clearOriginsInRegion(region)")
| {{region}}
.mb-2
.d-flex.align-items-center.mb-1(*ngFor="let o of origins")
origin-info-remote.flex-grow-1([originIP]="o.ip")
small.text-muted
from-now.mr-1([time]="o.last")
button.btn.btn-xs.btn-default((click)="removeOrigin(o.ip)" title="Remove origin")
fa-icon([icon]="trashIcon")
hr
//- duplicates
.d-flex.justify-content-between.align-items-start
h5
| Duplicates #[span.text-muted ({{duplicates?.length}})]
fa-icon.text-muted.ml-1(*ngIf="loadingDuplicates" [icon]="spinnerIcon" [fixedWidth]="true" [spin]="true")
div
button.btn.btn-xs.btn-default((click)="clearOriginsFromDuplicates()" title="Remove all origins older than 14 days from all duplicates")
fa-icon.mr-1([icon]="trashIcon")
| old
button.btn.btn-xs.btn-default.ml-1((click)="refresh()" title="Refresh duplicates")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
.mb-2
.text-muted(*ngIf="!duplicates || !duplicates.length")
| - no duplicates -
.mb-1.d-flex.align-items-center(*ngFor="let d of duplicates | slice:0:duplicatesLimit")
ng-template(#duplicatePonies)
.text-left(*ngIf="d.ponies")
div(*ngFor="let p of d.ponies")
| {{p}}
span.badge.badge-none(title="Duplicate origins" [class.deleted]="d.origins === 0")
| {{d.origins}}
span.badge.badge-none.ml-1(
title="Duplicate ponies" [class.deleted]="!d.ponies || !d.ponies.length"
[tooltip]="duplicatePonies" [isDisabled]="!d.ponies || !d.ponies.length")
| {{d.ponies ? d.ponies.length : '-'}}
fa-icon.ml-1.text-alert(
*ngIf="d.note" [icon]="duplicateNoteIcon" title="Duplicate in note")
fa-icon.ml-1.text-muted(
*ngIf="d.browserId" [icon]="duplicateBrowserIdIcon" title="Duplicate browser ID")
fa-icon.ml-1.text-muted(
*ngIf="d.userAgent" [icon]="duplicateBrowserIcon" [tooltip]="d.userAgent" title="Duplicate browser")
fa-icon.ml-1(
*ngIf="d.emails" [icon]="duplicateEmailIcon" title="Duplicate emails: {{d.emails}}"
[ngClass]="d.indenticalEmail ? 'text-alert' : 'text-muted'")
fa-icon.ml-1.text-alert(*ngIf="d.birthdate" [icon]="duplicateBirthdateIcon" title="Duplicate birthdate")
fa-icon.ml-1.text-muted(*ngIf="d.name" [icon]="duplicateNameIcon" title="Duplicate name")
account-info-remote.ml-1(#info [accountId]="d.account" [extendedAuths]="true")
button.btn.btn-xs.btn-default.ml-1((click)="merge(d.account)" title="Merge account")
fa-icon([icon]="mergeIcon" size="lg")
button.btn.btn-xs.btn-default.ml-1((click)="chatLog.add(info.account)" title="Add to chat log")
fa-icon([icon]="commentIcon" [fixedWidth]="true")
a.text-muted(*ngIf="duplicates && duplicates.length > duplicatesLimit" (click)="duplicatesLimit = 9999999")
| more ...
a.text-muted(*ngIf="duplicates && duplicatesLimit === 9999999" (click)="duplicatesLimit = 10")
| less ...
hr
//- around
.d-flex.justify-content-between.align-items-start
h5 Around #[span.text-muted ({{around ? around.length : '-'}})]
button.btn.btn-xs.btn-default((click)="refreshAround()" title="Refresh around")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of around")
span.badge.badge-none(title="Distance in tiles from user") {{a.distance.toFixed(1)}}
fa-icon.ml-1.text-alert(*ngIf="a.party" [icon]="partyIcon" title="In party with user")
account-info-remote.ml-1(#info [accountId]="a.account" [extendedAuths]="true" [showDuplicates]="true")
button.btn.btn-xs.btn-default.ml-1((click)="merge(a.account)" title="Merge account")
fa-icon([icon]="mergeIcon" size="lg")
button.btn.btn-xs.btn-default.ml-1((click)="chatLog.add(info.account)" title="Add to chat log")
fa-icon([icon]="commentIcon" [fixedWidth]="true")
hr
//- friends
.d-flex.justify-content-between.align-items-start
h5 Friends #[span.text-muted ({{friends ? friends.length : '?'}})]
button.btn.btn-xs.btn-default((click)="fetchFriends()" title="Fetch friends list")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of friends | slice:0:friendsLimit")
account-info-remote([accountId]="a")
button.btn.btn-xs.btn-default.ml-1((click)="removeFriend(a)" title="Remove friend")
fa-icon([icon]="trashIcon")
a.text-muted(*ngIf="friends && friends.length > friendsLimit" (click)="friendsLimit = 9999999")
| more ...
//- supporter invites
//- h5 Supporter invites #[span.text-muted ({{invites?.length || 0}})]
//- div(*ngIf="invites?.length")
.mb-1.d-flex.align-items-center(*ngFor="let i of invites")
.text-muted.mr-2 {{i.type}}
fa-icon.mr-2([icon]="checkIcon" [ngClass]="i.active ? 'text-success' : 'text-danger'")
account-info-remote.ml-1([accountId]="i.target" [extendedAuths]="true")
small.text-muted.flex-grow-1.text-right
time-field([time]="i.createdAt")
hr
//- force hides
div(*ngIf="account.hides && account.hides.length")
.d-flex.justify-content-between.align-items-start
h5 Force hidden #[span.text-muted ({{account.hides.length}})]
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of account.hides")
account-info-remote([accountId]="a")
hr
//- hides
.d-flex.justify-content-between.align-items-start
h5 Hidden by #[span.text-muted ({{hiddenBy ? hiddenBy.length : '?'}})]
button.btn.btn-xs.btn-default((click)="fetchHidden()" title="Fetch hidden list")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of hiddenBy | slice:0:hiddenByLimit")
account-info-remote([accountId]="a")
a.text-muted(*ngIf="hiddenBy && hiddenBy.length > hiddenByLimit" (click)="hiddenByLimit = 9999999")
| more ...
h5 Hides users #[span.text-muted ({{hidden ? hidden.length : '?'}})]
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of hidden | slice:0:hiddenLimit")
account-info-remote([accountId]="a")
a.text-muted(*ngIf="hidden && hidden.length > hiddenLimit" (click)="hiddenLimit = 9999999")
| more ...
//- hides (perma)
.d-flex.justify-content-between.align-items-start
h5 Perma hidden by #[span.text-muted ({{permaHiddenBy ? permaHiddenBy.length : '?'}})]
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of permaHiddenBy | slice:0:permaHiddenByLimit")
account-info-remote([accountId]="a")
a.text-muted(*ngIf="permaHiddenBy && permaHiddenBy.length > permaHiddenByLimit" (click)="permaHiddenByLimit = 9999999")
| more ...
h5 Perma hides users #[span.text-muted ({{permaHidden ? permaHidden.length : '?'}})]
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of permaHidden | slice:0:permaHiddenLimit")
account-info-remote([accountId]="a")
a.text-muted(*ngIf="permaHidden && permaHidden.length > permaHiddenLimit" (click)="permaHiddenLimit = 9999999")
| more ...
hr
//- ignores
.d-flex.justify-content-between.align-items-start
h5 Ignored by #[span.text-muted ({{ignoredBy ? ignoredBy.length : account.ignoresCount}})]
button.btn.btn-xs.btn-default((click)="fetchIgnores()" title="Fetch ignores list")
fa-icon([icon]="syncIcon" [fixedWidth]="true")
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of ignoredBy | slice:0:ignoredByLimit")
account-info-remote([accountId]="a")
button.btn.btn-xs.btn-default.ml-1((click)="removeIgnore(a._id, account._id)" title="Remove ignore")
fa-icon([icon]="trashIcon")
a.text-muted(*ngIf="ignoredBy && ignoredBy.length > ignoredByLimit" (click)="ignoredByLimit = 9999999")
| more ...
h5 Ignores users #[span.text-muted ({{ignores ? ignores.length : '?'}})]
.mb-2
.mb-1.d-flex.align-items-center(*ngFor="let a of ignores | slice:0:ignoresLimit")
account-info-remote([accountId]="a")
button.btn.btn-xs.btn-default.ml-1((click)="removeIgnore(account._id, a._id)" title="Remove ignore")
fa-icon([icon]="trashIcon")
a.text-muted(*ngIf="ignores && ignores.length > ignoresLimit" (click)="ignoresLimit = 9999999")
| more ...
.col-md-3
//- emails
h5.d-flex.justify-content-between
div Emails #[span.text-muted ({{account.emails?.length || 0}})]
.dropdown(dropdown)
button.btn.btn-xs.btn-default.dropdown-toggle(dropdownToggle)
fa-icon([icon]="cogIcon" [fixedWidth]="true")
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
a.dropdown-item((click)="addEmail()")
| Add email
.mb-2
.text-muted(*ngIf="!account.emails?.length")
| - no emails -
.d-flex.justify-content-between.items-align-center.mb-1(*ngFor="let e of account.emails | orderBy | slice:0:emailLimit")
div {{e}}
button.btn.btn-xs.btn-default((click)="removeEmail(e)" title="Remove email")
fa-icon([icon]="trashIcon")
a.text-muted((click)="emailLimit = 99999" *ngIf="emailLimit < (account.emails?.length || 0)")
| more ...
//- ponies
h5.d-flex.justify-content-between
div Ponies #[span.text-muted ({{account.characterCount}})]
.dropdown(dropdown)
button.btn.btn-xs.btn-default.dropdown-toggle(dropdownToggle)
fa-icon([icon]="cogIcon" [fixedWidth]="true")
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
a.dropdown-item((click)="getPoniesCreators()")
| Fetch creator info
a.dropdown-item((click)="restoringPonies = true")
| Restore ponies
a.dropdown-item((click)="removePoniesAboveLimit()")
| Remove ponies above limit
a.dropdown-item.text-danger((click)="removeAllPonies()")
| Remove all ponies
.mb-2(*ngIf="restoringPonies")
textarea.form-control.mb-1([(ngModel)]="poniesToRestore" placeholder="paste removed pony logs here")
textarea.form-control.mb-1([(ngModel)]="poniesToRestoreFilter" placeholder="paste pony ids to use here (optional)")
.text-right
button.btn.btn-xs.btn-default((click)="poniesToRestore = ''; restoringPonies = false") cancel
button.btn.btn-xs.btn-success.ml-1((click)="restorePonies()" [disabled]="!poniesToRestore") restore
hr
.mb-2
pony-list-remote(
[accountId]="id" [expanded]="true" [deletable]="true" [highlight]="highlighCharacter" [duplicates]="duplicates")
.row
.col-md-12
h5 Events
events-table([events]="events" (showChat)="chatLog.show(account)")
.row
.col-md-12
admin-chat-log(#chatLog [canClose]="false" [account]="account")
button.btn.btn-sm.btn-default((click)="printJSON()") raw data
.well.pre(*ngIf="rawData")
| {{rawData}}
@@ -0,0 +1,17 @@
@import 'partials/variables';
.spoiler-text {
background: $body-color;
border-radius: 3px;
cursor: default;
&:hover {
background: none;
}
}
.long-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@@ -0,0 +1,582 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { remove, uniq } from 'lodash';
import {
Account, Event, Auth, ROLES, Character, MergeInfo, MergeAccountData, SupporterInvite, accountFlags,
OriginInfoBase, DuplicateResult, AroundEntry, LogEntry
} from '../../../common/adminInterfaces';
import { compareByName, createSupporterChanges, SupporterChange, getTranslationUrl, getAge } from '../../../common/adminUtils';
import { hasRole } from '../../../common/accountUtils';
import { AdminModel } from '../../services/adminModel';
import {
faCog, faSync, faLanguage, faTrash, faGlobe, faTerminal, faSpinner,
faIdBadge, faEnvelope, faFont, faCompressArrowsAlt, faComment, faSignOutAlt, faUsers, faCheckCircle,
faDatabase, faCopy, faCalendar, faExclamationCircle,
} from '../../../client/icons';
import { flagsToString, includes, flatten, removeItem } from '../../../common/utils';
import { Subscription } from '../../../common/interfaces';
import { showTextInNewTab } from '../../../client/htmlUtils';
const defaultLimit = 15;
const defaultDuplicatesLimit = 10;
const year = (new Date()).getFullYear();
@Component({
selector: 'admin-account-details',
templateUrl: 'admin-account-details.pug',
styleUrls: ['admin-account-details.scss'],
})
export class AdminAccountDetails implements OnInit, OnDestroy {
readonly cogIcon = faCog;
readonly syncIcon = faSync;
readonly langIcon = faLanguage;
readonly trashIcon = faTrash;
readonly signOutIcon = faSignOutAlt;
readonly duplicateNoteIcon = faExclamationCircle;
readonly duplicateBrowserIcon = faGlobe;
readonly duplicateBrowserIdIcon = faIdBadge;
readonly duplicateEmailIcon = faEnvelope;
readonly duplicateNameIcon = faFont;
readonly duplicateBirthdateIcon = faCalendar;
readonly mergeIcon = faCompressArrowsAlt;
readonly commentIcon = faComment;
readonly partyIcon = faUsers;
readonly checkIcon = faCheckCircle;
readonly consoleIcon = faTerminal;
readonly spinnerIcon = faSpinner;
readonly dataIcon = faDatabase;
readonly copyIcon = faCopy;
readonly roles = ROLES;
ages = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];
account?: Account;
duplicates?: DuplicateResult[];
ponyNames?: string[];
ignores?: string[];
ignoredBy?: string[];
hidden?: string[];
hiddenBy?: string[];
permaHidden?: string[];
permaHiddenBy?: string[];
friends?: string[];
events?: Event[];
merges?: MergeInfo[];
banLog?: LogEntry[];
support?: SupporterChange[];
rawData?: string;
invites?: SupporterInvite[];
auths: string[] = [];
origins: OriginInfoBase[] = [];
counters: { key: string; value: any; }[] = [];
authLimit = defaultLimit;
emailLimit = defaultLimit;
loadingDuplicates = false;
accountObject?: Account;
clearingSessions = false;
authData?: string;
authDataAccount?: string;
restoringPonies = false;
poniesToRestore = '';
poniesToRestoreFilter = '';
friendsLimit = defaultLimit;
hiddenByLimit = defaultLimit;
hiddenLimit = defaultLimit;
permaHiddenByLimit = defaultLimit;
permaHiddenLimit = defaultLimit;
private id?: string;
private aroundMap = new Map<string, AroundEntry[]>();
private duplicatesLimits = new Map<string, number>();
private ignoresLimits = new Map<string, number>();
private ignoredByLimits = new Map<string, number>();
private authsSubscription?: Subscription;
private accountSubscription?: Subscription;
private originsSubscription?: Subscription;
constructor(private route: ActivatedRoute, private model: AdminModel) {
}
get canRemove() {
return hasRole(this.model.account, 'superadmin');
}
get around() {
return this.aroundMap.get(this.id || '');
}
get duplicatesLimit() {
return (this.id && this.duplicatesLimits.has(this.id)) ? this.duplicatesLimits.get(this.id)! : defaultDuplicatesLimit;
}
set duplicatesLimit(value) {
if (this.id) {
this.duplicatesLimits.set(this.id, value);
}
}
get ignoresLimit() {
return (this.id && this.ignoresLimits.has(this.id)) ? this.ignoresLimits.get(this.id)! : defaultLimit;
}
set ignoresLimit(value) {
if (this.id) {
this.ignoresLimits.set(this.id, value);
}
}
get ignoredByLimit() {
return (this.id && this.ignoredByLimits.has(this.id)) ? this.ignoredByLimits.get(this.id)! : defaultLimit;
}
set ignoredByLimit(value) {
if (this.id) {
this.ignoredByLimits.set(this.id, value);
}
}
get originRegions() {
return uniq(this.origins.map(o => o.country));
}
get age() {
return (this.account && this.account.birthdate) ? getAge(this.account.birthdate) : '-';
}
get forceAge() {
return (this.account && this.account.birthyear) ? (year - this.account.birthyear) : '-';
}
ngOnInit() {
this.route.params.subscribe(({ id }) => {
this.id = id;
this.update();
});
this.model.updated = () => this.update();
this.update();
}
ngOnDestroy() {
this.model.updated = () => { };
this.authsSubscription && this.authsSubscription.unsubscribe();
this.accountSubscription && this.accountSubscription.unsubscribe();
this.originsSubscription && this.originsSubscription.unsubscribe();
}
refresh() {
const account = this.account;
this.ponyNames = [];
this.banLog = undefined;
this.merges = undefined;
this.support = undefined;
this.invites = undefined;
this.accountObject = undefined;
this.loadingDuplicates = false;
this.authsSubscription && this.authsSubscription.unsubscribe();
this.authsSubscription = undefined;
this.originsSubscription && this.originsSubscription.unsubscribe();
this.originsSubscription = undefined;
this.auths = [];
this.origins = [];
this.counters = [];
this.friends = undefined;
this.hidden = undefined;
this.hiddenBy = undefined;
this.permaHidden = undefined;
this.permaHiddenBy = undefined;
if (account) {
account.ignoredByLimit = account.ignoredByLimit || 10;
account.ignoresLimit = account.ignoresLimit || 10;
account.duplicatesLimit = account.duplicatesLimit || 10;
this.loadingDuplicates = true;
this.model.getAllDuplicates(account._id)
.then(duplicates => {
if (duplicates) {
this.ponyNames = uniq(flatten(duplicates.map(d => (d.ponies || []).map(x => x.toLowerCase()))));
this.duplicates = duplicates;
this.loadingDuplicates = false;
}
});
this.model.getAccount(account._id)
.then(account => this.accountObject = account);
this.refreshDetails();
this.authsSubscription = this.model.accountAuths
.subscribe(account._id, auths => this.auths = auths || []);
this.originsSubscription = this.model.accountOrigins
.subscribe(account._id, origins => this.origins = origins || []);
} else {
this.duplicates = [];
this.ignores = [];
this.ignoredBy = [];
}
}
refreshAround() {
const account = this.account;
if (account) {
this.model.getAccountAround(account._id)
.then(accounts => {
if (accounts) {
this.aroundMap.set(account._id, accounts);
}
});
}
}
refreshDetails() {
function createCounter(key: string, value: any) {
if (key === 'toys') {
value = (value >>> 0).toString(2).padStart(32, '0');
}
return { key, value };
}
if (this.account) {
this.model.getDetailsForAccount(this.account)
.then(details => {
if (details) {
this.banLog = details.banLog;
this.merges = details.merges;
this.support = createSupporterChanges(details.supporterLog);
this.invites = [
...details.invitesSent!.map(i => ({ ...i, type: 'sent' })),
...details.invitesReceived!.map(i => ({ ...i, type: 'recv' })),
];
const state = details.state as any;
this.counters = Object.keys(state).map(key => createCounter(key, state[key]));
}
});
}
}
fetchIgnores() {
if (this.id) {
this.model.getIgnoresAndIgnoredBy(this.id)
.then(result => {
if (result) {
this.ignores = result.ignores;
this.ignoredBy = result.ignoredBy;
}
});
}
}
fetchHidden() {
if (this.id) {
this.model.getAccountHidden(this.id)
.then(result => {
if (result) {
this.hidden = result.hidden;
this.hiddenBy = result.hiddenBy;
this.permaHidden = result.permaHidden;
this.permaHiddenBy = result.permaHiddenBy;
}
});
}
}
fetchFriends() {
if (this.id) {
this.model.getAccountFriends(this.id)
.then(result => this.friends = result);
}
}
removeFriend(friendId: string) {
if (this.id && confirm('Are you sure ?')) {
this.model.removeFriend(this.id, friendId)
.then(() => this.friends && removeItem(this.friends, friendId));
}
}
canToggleRole(role: string) {
return role !== 'superadmin' && hasRole(this.model.account, 'superadmin');
}
hasRole(role: string) {
return hasRole(this.account, role);
}
toggleRole(role: string) {
if (this.id) {
this.model.setRole(this.id, role, !this.hasRole(role));
}
}
showAccountData() {
if (this.id) {
this.model.getAccount(this.id)
.then(account => {
(window as any).$data = account;
console.log(account);
console.log('accessible in $data');
});
}
}
printAuthList() {
this.forAuths(auths => {
const authList = auths.map((a, i) => `${i + 1}. [${a.provider}] ${a.name || '<no name>'}`).join('\n');
this.authData = authList;
this.authDataAccount = this.account ? `${this.account.name} [${this.account._id}]` : '';
console.log(authList);
});
}
printAuthData() {
this.forAuths(auths => {
const authData = auths.map((a, i) => `${i + 1}. [${a._id}] [${a.provider}] ${a.name || '<no name>'} (${a.emails})`).join('\n');
this.authData = authData;
this.authDataAccount = this.account ? `${this.account.name} [${this.account._id}]` : '';
console.log(authData);
});
}
copyAuthData() {
if ('clipboard' in navigator) {
(navigator as any).clipboard.writeText(this.authData);
}
}
clearAuthData() {
this.authData = undefined;
this.authDataAccount = undefined;
}
fetchAuths() {
this.forAuths(auths => {
(window as any).$auths = auths;
console.log(auths);
});
}
private forAuths(callback: (auths: Auth[]) => void) {
if (this.account) {
this.model.getAuthsForAccount(this.account._id)
.then(auths => callback(auths || []));
}
}
printJSON() {
if (this.id) {
this.model.getAccount(this.id)
.then(account => this.rawData = JSON.stringify(account, undefined, 2));
}
}
clearOrigins(old: boolean, singles: boolean) {
if (this.id) {
this.model.clearOriginsForAccount(this.id, { old, singles })
.then(() => this.refresh());
}
}
clearOriginsInRegion(country: string) {
if (this.id) {
this.model.clearOriginsForAccount(this.id, { country })
.then(() => this.refresh());
}
}
clearOriginsFromDuplicates() {
if (this.account) {
const duplicates = (this.duplicates || []).slice(0, this.account.duplicatesLimit || 0);
const accounts = duplicates.map(d => d.account);
this.model.clearOriginsForAccounts(accounts, { old: true })
.then(() => this.refresh());
}
}
removeOrigin(ip: string) {
if (this.id) {
this.model.removeOriginsForAccount(this.id, [ip])
.then(() => this.refresh());
}
}
merge(accountId: string) {
if (this.account && confirm('Are you sure?')) {
this.model.mergeAccounts(this.account._id, accountId)
.then(() => remove(this.duplicates || [], d => d.account === accountId))
.then(() => this.refresh());
}
}
remove() {
if (this.account && confirm('Are you sure?')) {
this.model.removeAccount(this.account._id);
}
}
translateUrl(text: string) {
return getTranslationUrl(text);
}
getPoniesCreators() {
if (this.account) {
this.model.getPoniesCreators(this.account._id)
.then(items => {
if (items) {
items.sort(compareByName);
(window as any).$ponies = items;
console.log(items.map(i => `[${i._id}] "${i.name}" ${i.creator}`).join('\n'));
}
});
}
}
removePoniesAboveLimit() {
if (this.account && confirm('Are you sure?')) {
this.model.removePoniesAboveLimit(this.account._id);
}
}
removeAllPonies() {
if (this.account && confirm('Are you sure?')) {
this.model.removeAllPonies(this.account._id);
}
}
restorePonies() {
if (this.account && this.poniesToRestore) {
let ids: string[] | undefined = undefined;
if (this.poniesToRestoreFilter) {
const matches = Array.from(this.poniesToRestoreFilter.match(/\[[a-f0-9]{24}\]/g) || [])
.map(id => id.substr(1, 24));
if (matches.length) {
ids = matches;
}
}
this.model.restorePonies(this.account._id, this.poniesToRestore, ids);
this.poniesToRestore = '';
this.restoringPonies = false;
}
}
removeEmail(email: string) {
if (this.account && confirm('Are you sure?')) {
this.model.removeEmail(this.account._id, email);
}
}
removeIgnore(account: string, ignoredAccount: string) {
this.model.removeIgnore(account, ignoredAccount)
.then(() => this.fetchIgnores());
}
highlighCharacter = (char: Character | undefined): boolean => {
return !!char && includes(this.ponyNames, char.name.toLowerCase());
}
clearSessions() {
if (this.account) {
this.clearingSessions = true;
this.model.clearSessions(this.account._id)
.finally(() => this.clearingSessions = false);
}
}
getMergeTooltip(merge: MergeInfo) {
function mergeInfo(title: string, account: MergeAccountData) {
return [
title,
`\tname: ${account.name}`,
`\tnote: ${account.note || ''}`,
`\tflags: ${flagsToString(account.flags, accountFlags)}`,
`\tage: ${account.birthdate ? getAge(account.birthdate) : '-'}`,
`\tfriends: ${account.friends ? account.friends.length : 0}`,
`\tcounters:`,
...Object.keys(account.counters || {}).sort().map(key => `\t\t${key}: ${(account.counters as any)[key]}`),
`\tstate:`,
...(account.state ? JSON.stringify(account.state, null, 2).split(/\n/).map(x => `\t\t${x}`) : []),
`\temails:`,
...account.emails.map(e => `\t\t${e}`),
`\tauths:`,
...account.auths.map(a => `\t\t[${a.id}] ${a.name}`),
`\tcharacters: ${account.characters.length}`,
].join('\n');
}
if (merge.data) {
return `${mergeInfo('ACCOUNT', merge.data.account)}\n\n${mergeInfo('MERGED', merge.data.merge)}`;
} else {
return '<empty>';
}
}
printMerge(merge: MergeInfo) {
console.log(this.mergeInfo(merge));
}
printMerge2(merge: MergeInfo) {
console.log(this.mergeInfo2(merge));
}
mergeInfo(merge: MergeInfo) {
function mergeInfo(title: string, account: MergeAccountData) {
return [
title,
`\tname: ${account.name}`,
`\tnote: ${account.note || ''}`,
`\tflags: ${flagsToString(account.flags, accountFlags)}`,
`\tage: ${account.birthdate ? getAge(account.birthdate) : '-'}`,
`\tcounters:`,
...Object.keys(account.counters || {}).sort().map(key => `\t\t${key}: ${(account.counters as any)[key]}`),
`\tstate:`,
...(account.state ? JSON.stringify(account.state, null, 2).split(/\n/).map(x => `\t\t${x}`) : []),
`\temails:`,
...account.emails.map(e => `\t\t${e}`),
`\tauths:`,
...account.auths.map(a => `\t\t[${a.id}] ${a.name}`),
`\tcharacters:`,
...account.characters.map(a => `\t\t[${a.id}] ${a.name}`),
`\tfriends:`,
...(account.friends || []).map(i => `\t\t[${i}]`),
`\tignores:`,
...(account.ignores || []).map(i => `\t\t[${i}]`),
].join('\n');
}
return merge.data && `${mergeInfo('ACCOUNT', merge.data.account)}\n\n${mergeInfo('MERGED', merge.data.merge)}`;
}
private mergeInfo2(merge: MergeInfo) {
function mergeInfo(title: string, account: MergeAccountData) {
return [
title,
`\tname: ${JSON.stringify(account.name)}`,
`\tnote: ${JSON.stringify(account.note)}`,
`\tflags: ${JSON.stringify(account.flags)}`,
`\tcounters: ${JSON.stringify(account.counters)}`,
`\tstate: ${JSON.stringify(account.state)}`,
`\temails: ${JSON.stringify(account.emails)}`,
`\tauths: ${JSON.stringify(account.auths)}`,
`\tcharacters: ${JSON.stringify(account.characters)}`,
`\tfriends: ${JSON.stringify(account.friends)}`,
`\tignores: ${JSON.stringify(account.ignores)}`,
].join('\n');
}
return merge.data && `${mergeInfo('ACCOUNT', merge.data.account)}\n\n${mergeInfo('MERGED', merge.data.merge)}`;
}
showMergeInNewTab(merge: MergeInfo) {
showTextInNewTab(this.mergeInfo(merge) || '');
}
showMergeInNewTab2(merge: MergeInfo) {
showTextInNewTab(this.mergeInfo2(merge) || '');
}
unmerge(mergeId: string | undefined, split: MergeAccountData, keep: MergeAccountData) {
if (this.account) {
this.model.unmergeAccounts(this.account._id, mergeId, split, keep)
.then(() => this.refresh());
}
}
setAge(age: number) {
if (this.account) {
this.model.setAge(this.account._id, age);
}
}
addEmail() {
if (this.account) {
const email = prompt('enter email');
if (email && /@/.test(email)) {
this.model.addEmail(this.account._id, email);
}
}
}
private update() {
if (this.id) {
this.accountSubscription && this.accountSubscription.unsubscribe();
this.accountSubscription = this.id ? this.model.accounts.subscribe(this.id, a => this.setAccount(a)) : undefined;
this.events = this.model.events
.filter(e => e.account === this.id)
.slice(0, 10);
}
}
private setAccount(account: Account | undefined) {
const theSame = this.account === account || (this.account && account && this.account._id === account._id);
this.account = account;
if (!theSame) {
this.duplicates = [];
this.ponyNames = [];
this.ignores = [];
this.ignoredBy = [];
this.rawData = undefined;
this.authLimit = defaultLimit;
this.emailLimit = defaultLimit;
this.duplicatesLimit = defaultDuplicatesLimit;
this.refresh();
this.friendsLimit = defaultLimit;
this.hiddenByLimit = defaultLimit;
this.hiddenLimit = defaultLimit;
this.permaHiddenByLimit = defaultLimit;
this.permaHiddenLimit = defaultLimit;
(window as any).$account = account;
}
}
}