mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 21:55:52 +02:00
Revert codestyle changes (#40)
* Revert "7f7efbb94bab8574e42d942ad82d414e007b2970" Code style changes should probably be part of a PR
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -12,112 +12,112 @@ let currentPage = 0;
|
||||
let not = false;
|
||||
|
||||
@Component({
|
||||
selector: 'admin-accounts',
|
||||
templateUrl: 'admin-accounts.pug',
|
||||
selector: 'admin-accounts',
|
||||
templateUrl: 'admin-accounts.pug',
|
||||
})
|
||||
export class AdminAccounts implements OnInit {
|
||||
readonly syncIcon = faSync;
|
||||
readonly filterIcon = faFilter;
|
||||
readonly commentIcon = faComment;
|
||||
readonly eraserIcon = faEraser;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly filters = [
|
||||
'all',
|
||||
'banned',
|
||||
'timed out',
|
||||
'with flags',
|
||||
'notes',
|
||||
'supporters',
|
||||
];
|
||||
totalItems = 99999999;
|
||||
itemsOnPageIds: string[] = [];
|
||||
itemsPerPage = 20;
|
||||
loading = false;
|
||||
private expanded = new Set<string>();
|
||||
constructor(public model: AdminModel, private router: Router) {
|
||||
}
|
||||
get showOnly() {
|
||||
return showOnly;
|
||||
}
|
||||
set showOnly(value) {
|
||||
if (showOnly !== value) {
|
||||
showOnly = value;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
get not() {
|
||||
return not;
|
||||
}
|
||||
set not(value) {
|
||||
if (not !== value) {
|
||||
not = value;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
get autoRefresh() {
|
||||
return autoRefresh;
|
||||
}
|
||||
set autoRefresh(value) {
|
||||
autoRefresh = value;
|
||||
}
|
||||
get currentPage() {
|
||||
return currentPage;
|
||||
}
|
||||
set currentPage(value) {
|
||||
if (currentPage !== value) {
|
||||
currentPage = value;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
get search() {
|
||||
return search;
|
||||
}
|
||||
set search(value) {
|
||||
if (search !== value) {
|
||||
search = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
private execSearch = debounce(() => this.refresh(), 500);
|
||||
get duplicateEntries() {
|
||||
return this.model.duplicateEntries;
|
||||
}
|
||||
refreshDuplicates() {
|
||||
this.model.checkDuplicateEntries(true);
|
||||
}
|
||||
limit(account: Account) {
|
||||
return this.expanded.has(account._id) ? 99999 : 2;
|
||||
}
|
||||
expand(account: Account) {
|
||||
this.expanded.add(account._id);
|
||||
}
|
||||
ngOnInit() {
|
||||
this.model.accountPromise
|
||||
.then(() => this.refresh());
|
||||
}
|
||||
refresh(force = false) {
|
||||
this.loading = true;
|
||||
this.model.findAccounts({
|
||||
search: this.search.trim(),
|
||||
not: this.not,
|
||||
showOnly: this.showOnly,
|
||||
page: this.currentPage - 1,
|
||||
itemsPerPage: this.itemsPerPage,
|
||||
force,
|
||||
}).then(result => {
|
||||
if (result && result.page === (this.currentPage - 1)) {
|
||||
this.totalItems = result.totalItems;
|
||||
this.itemsOnPageIds = result.accounts;
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
createAccount() {
|
||||
const name = prompt('enter new account name');
|
||||
readonly syncIcon = faSync;
|
||||
readonly filterIcon = faFilter;
|
||||
readonly commentIcon = faComment;
|
||||
readonly eraserIcon = faEraser;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly filters = [
|
||||
'all',
|
||||
'banned',
|
||||
'timed out',
|
||||
'with flags',
|
||||
'notes',
|
||||
'supporters',
|
||||
];
|
||||
totalItems = 99999999;
|
||||
itemsOnPageIds: string[] = [];
|
||||
itemsPerPage = 20;
|
||||
loading = false;
|
||||
private expanded = new Set<string>();
|
||||
constructor(public model: AdminModel, private router: Router) {
|
||||
}
|
||||
get showOnly() {
|
||||
return showOnly;
|
||||
}
|
||||
set showOnly(value) {
|
||||
if (showOnly !== value) {
|
||||
showOnly = value;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
get not() {
|
||||
return not;
|
||||
}
|
||||
set not(value) {
|
||||
if (not !== value) {
|
||||
not = value;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
get autoRefresh() {
|
||||
return autoRefresh;
|
||||
}
|
||||
set autoRefresh(value) {
|
||||
autoRefresh = value;
|
||||
}
|
||||
get currentPage() {
|
||||
return currentPage;
|
||||
}
|
||||
set currentPage(value) {
|
||||
if (currentPage !== value) {
|
||||
currentPage = value;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
get search() {
|
||||
return search;
|
||||
}
|
||||
set search(value) {
|
||||
if (search !== value) {
|
||||
search = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
private execSearch = debounce(() => this.refresh(), 500);
|
||||
get duplicateEntries() {
|
||||
return this.model.duplicateEntries;
|
||||
}
|
||||
refreshDuplicates() {
|
||||
this.model.checkDuplicateEntries(true);
|
||||
}
|
||||
limit(account: Account) {
|
||||
return this.expanded.has(account._id) ? 99999 : 2;
|
||||
}
|
||||
expand(account: Account) {
|
||||
this.expanded.add(account._id);
|
||||
}
|
||||
ngOnInit() {
|
||||
this.model.accountPromise
|
||||
.then(() => this.refresh());
|
||||
}
|
||||
refresh(force = false) {
|
||||
this.loading = true;
|
||||
this.model.findAccounts({
|
||||
search: this.search.trim(),
|
||||
not: this.not,
|
||||
showOnly: this.showOnly,
|
||||
page: this.currentPage - 1,
|
||||
itemsPerPage: this.itemsPerPage,
|
||||
force,
|
||||
}).then(result => {
|
||||
if (result && result.page === (this.currentPage - 1)) {
|
||||
this.totalItems = result.totalItems;
|
||||
this.itemsOnPageIds = result.accounts;
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
createAccount() {
|
||||
const name = prompt('enter new account name');
|
||||
|
||||
if (name) {
|
||||
this.model.createAccount(name)
|
||||
.then(id => this.router.navigate(['accounts', id]));
|
||||
}
|
||||
}
|
||||
if (name) {
|
||||
this.model.createAccount(name)
|
||||
.then(id => this.router.navigate(['accounts', id]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,95 +5,95 @@ import { AdminModel } from '../../services/adminModel';
|
||||
import { BaseTable, BaseTableState } from '../base-table';
|
||||
import { AdminChatLog } from '../shared/admin-chat-log/admin-chat-log';
|
||||
import {
|
||||
faBell, faSync, faClock, faTrash, faComments, faHdd, faMicrochip, faCertificate, faClone, faPatreon
|
||||
faBell, faSync, faClock, faTrash, faComments, faHdd, faMicrochip, faCertificate, faClone, faPatreon
|
||||
} from '../../../client/icons';
|
||||
|
||||
let state: BaseTableState;
|
||||
|
||||
@Component({
|
||||
selector: 'admin-events',
|
||||
templateUrl: 'admin-events.pug',
|
||||
selector: 'admin-events',
|
||||
templateUrl: 'admin-events.pug',
|
||||
})
|
||||
export class AdminEvents extends BaseTable<Event> implements OnInit, OnDestroy {
|
||||
readonly bellIcon = faBell;
|
||||
readonly syncIcon = faSync;
|
||||
readonly clockIcon = faClock;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly commentsIcon = faComments;
|
||||
readonly hddIcon = faHdd;
|
||||
readonly ramIcon = faMicrochip;
|
||||
readonly certificateIcon = faCertificate;
|
||||
readonly duplicateIcon = faClone;
|
||||
readonly patreonIcon = faPatreon;
|
||||
@ViewChild('chatLog', { static: true }) chatLog!: AdminChatLog;
|
||||
private chatEvent?: Event;
|
||||
constructor(private model: AdminModel) {
|
||||
super();
|
||||
}
|
||||
get status() {
|
||||
return this.model.state.status;
|
||||
}
|
||||
get isLowDiskSpace() {
|
||||
return this.model.isLowDiskSpace;
|
||||
}
|
||||
get isLowMemory() {
|
||||
return this.model.isLowMemory;
|
||||
}
|
||||
get isOldCertificate() {
|
||||
return this.model.isOldCertificate;
|
||||
}
|
||||
get isOldPatreon() {
|
||||
return this.model.isOldPatreon;
|
||||
}
|
||||
get items() {
|
||||
return this.model.events;
|
||||
}
|
||||
get duplicateEntries() {
|
||||
return this.model.duplicateEntries;
|
||||
}
|
||||
get notifications() {
|
||||
return this.model.notifications;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.model.updated = () => this.updateItems();
|
||||
this.setState(state);
|
||||
this.updateItems();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.model.updated = () => { };
|
||||
}
|
||||
cleanupDeleted() {
|
||||
this.model.cleanupDeletedEvents();
|
||||
}
|
||||
refreshDuplicates() {
|
||||
this.model.checkDuplicateEntries(true);
|
||||
}
|
||||
removeEvents(olderThan: number) {
|
||||
const date = fromNow(-olderThan);
|
||||
const oldEvents = this.items.filter(e => e.updatedAt.getTime() < date.getTime());
|
||||
return Promise.all(oldEvents.map(e => this.model.removeEvent(e._id).then(() => this.removedEvent(e))));
|
||||
}
|
||||
showChat(e?: ChatEvent) {
|
||||
this.chatEvent = e && e.event;
|
||||
this.chatLog.show(e && e.account);
|
||||
}
|
||||
addChat(e: ChatEvent) {
|
||||
if (e.account) {
|
||||
this.chatLog.add(e.account);
|
||||
}
|
||||
}
|
||||
removedEvent(e: Event) {
|
||||
if (this.chatEvent === e) {
|
||||
this.chatLog.close();
|
||||
}
|
||||
}
|
||||
toggleNotifications() {
|
||||
this.model.toggleNotifications();
|
||||
}
|
||||
protected onChange() {
|
||||
state = this.getState();
|
||||
}
|
||||
protected updatePage() {
|
||||
super.updatePage();
|
||||
}
|
||||
readonly bellIcon = faBell;
|
||||
readonly syncIcon = faSync;
|
||||
readonly clockIcon = faClock;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly commentsIcon = faComments;
|
||||
readonly hddIcon = faHdd;
|
||||
readonly ramIcon = faMicrochip;
|
||||
readonly certificateIcon = faCertificate;
|
||||
readonly duplicateIcon = faClone;
|
||||
readonly patreonIcon = faPatreon;
|
||||
@ViewChild('chatLog', { static: true }) chatLog!: AdminChatLog;
|
||||
private chatEvent?: Event;
|
||||
constructor(private model: AdminModel) {
|
||||
super();
|
||||
}
|
||||
get status() {
|
||||
return this.model.state.status;
|
||||
}
|
||||
get isLowDiskSpace() {
|
||||
return this.model.isLowDiskSpace;
|
||||
}
|
||||
get isLowMemory() {
|
||||
return this.model.isLowMemory;
|
||||
}
|
||||
get isOldCertificate() {
|
||||
return this.model.isOldCertificate;
|
||||
}
|
||||
get isOldPatreon() {
|
||||
return this.model.isOldPatreon;
|
||||
}
|
||||
get items() {
|
||||
return this.model.events;
|
||||
}
|
||||
get duplicateEntries() {
|
||||
return this.model.duplicateEntries;
|
||||
}
|
||||
get notifications() {
|
||||
return this.model.notifications;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.model.updated = () => this.updateItems();
|
||||
this.setState(state);
|
||||
this.updateItems();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.model.updated = () => { };
|
||||
}
|
||||
cleanupDeleted() {
|
||||
this.model.cleanupDeletedEvents();
|
||||
}
|
||||
refreshDuplicates() {
|
||||
this.model.checkDuplicateEntries(true);
|
||||
}
|
||||
removeEvents(olderThan: number) {
|
||||
const date = fromNow(-olderThan);
|
||||
const oldEvents = this.items.filter(e => e.updatedAt.getTime() < date.getTime());
|
||||
return Promise.all(oldEvents.map(e => this.model.removeEvent(e._id).then(() => this.removedEvent(e))));
|
||||
}
|
||||
showChat(e?: ChatEvent) {
|
||||
this.chatEvent = e && e.event;
|
||||
this.chatLog.show(e && e.account);
|
||||
}
|
||||
addChat(e: ChatEvent) {
|
||||
if (e.account) {
|
||||
this.chatLog.add(e.account);
|
||||
}
|
||||
}
|
||||
removedEvent(e: Event) {
|
||||
if (this.chatEvent === e) {
|
||||
this.chatLog.close();
|
||||
}
|
||||
}
|
||||
toggleNotifications() {
|
||||
this.model.toggleNotifications();
|
||||
}
|
||||
protected onChange() {
|
||||
state = this.getState();
|
||||
}
|
||||
protected updatePage() {
|
||||
super.updatePage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,33 +4,33 @@ import { Event } from '../../../common/adminInterfaces';
|
||||
import { AdminModel } from '../../services/adminModel';
|
||||
|
||||
@Component({
|
||||
selector: 'admin-origin-details',
|
||||
templateUrl: 'admin-origin-details.pug',
|
||||
selector: 'admin-origin-details',
|
||||
templateUrl: 'admin-origin-details.pug',
|
||||
})
|
||||
export class AdminOriginDetails implements OnInit {
|
||||
events?: Event[];
|
||||
accounts: string[] = [];
|
||||
ip?: string;
|
||||
constructor(private route: ActivatedRoute, private model: AdminModel) {
|
||||
}
|
||||
get whoisHref() {
|
||||
return `http://whois.urih.com/record/${this.ip}/`;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.route.params.forEach(p => {
|
||||
this.ip = p['ip'];
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
private update() {
|
||||
this.accounts = [];
|
||||
events?: Event[];
|
||||
accounts: string[] = [];
|
||||
ip?: string;
|
||||
constructor(private route: ActivatedRoute, private model: AdminModel) {
|
||||
}
|
||||
get whoisHref() {
|
||||
return `http://whois.urih.com/record/${this.ip}/`;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.route.params.forEach(p => {
|
||||
this.ip = p['ip'];
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
private update() {
|
||||
this.accounts = [];
|
||||
|
||||
if (this.model.connected && this.ip) {
|
||||
this.model.getAccountsByOrigin(this.ip)
|
||||
.then(accounts => this.accounts = accounts || []);
|
||||
this.events = this.model.events
|
||||
.filter(e => e.origin && e.origin.ip === this.ip)
|
||||
.slice(0, 20);
|
||||
}
|
||||
}
|
||||
if (this.model.connected && this.ip) {
|
||||
this.model.getAccountsByOrigin(this.ip)
|
||||
.then(accounts => this.accounts = accounts || []);
|
||||
this.events = this.model.events
|
||||
.filter(e => e.origin && e.origin.ip === this.ip)
|
||||
.slice(0, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,50 +4,50 @@ import { faSync, faEraser, faClock, faUser, faChevronDown, faSpinner } from '../
|
||||
import { AdminModel } from '../../services/adminModel';
|
||||
|
||||
@Component({
|
||||
selector: 'admin-origins',
|
||||
templateUrl: 'admin-origins.pug',
|
||||
selector: 'admin-origins',
|
||||
templateUrl: 'admin-origins.pug',
|
||||
})
|
||||
export class AdminOrigins implements OnInit {
|
||||
readonly syncIcon = faSync;
|
||||
readonly eraserIcon = faEraser;
|
||||
readonly clockIcon = faClock;
|
||||
readonly userIcon = faUser;
|
||||
readonly chevronDownIcon = faChevronDown;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
stats?: OriginStats;
|
||||
other?: OtherStats;
|
||||
requestStats: RequestStats[] = [];
|
||||
pending = false;
|
||||
constructor(public model: AdminModel) {
|
||||
}
|
||||
ngOnInit() {
|
||||
if (this.model.connected) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
update() {
|
||||
this.model.getOriginStats().then(stats => this.stats = stats);
|
||||
this.model.getOtherStats().then(stats => this.other = stats);
|
||||
}
|
||||
clear(count: number, andHigher = false) {
|
||||
this.clearAll(count, andHigher, true, true, false);
|
||||
}
|
||||
clearOld(count: number) {
|
||||
this.clearAll(count, true, true, false, false);
|
||||
}
|
||||
clearSingles(count: number) {
|
||||
this.clearAll(count, true, false, true, false);
|
||||
}
|
||||
clearTo10(count: number) {
|
||||
this.clearAll(count, true, false, true, true);
|
||||
}
|
||||
clearAll(count: number, andHigher: boolean, old: boolean, singles: boolean, trim: boolean) {
|
||||
this.pending = true;
|
||||
readonly syncIcon = faSync;
|
||||
readonly eraserIcon = faEraser;
|
||||
readonly clockIcon = faClock;
|
||||
readonly userIcon = faUser;
|
||||
readonly chevronDownIcon = faChevronDown;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
stats?: OriginStats;
|
||||
other?: OtherStats;
|
||||
requestStats: RequestStats[] = [];
|
||||
pending = false;
|
||||
constructor(public model: AdminModel) {
|
||||
}
|
||||
ngOnInit() {
|
||||
if (this.model.connected) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
update() {
|
||||
this.model.getOriginStats().then(stats => this.stats = stats);
|
||||
this.model.getOtherStats().then(stats => this.other = stats);
|
||||
}
|
||||
clear(count: number, andHigher = false) {
|
||||
this.clearAll(count, andHigher, true, true, false);
|
||||
}
|
||||
clearOld(count: number) {
|
||||
this.clearAll(count, true, true, false, false);
|
||||
}
|
||||
clearSingles(count: number) {
|
||||
this.clearAll(count, true, false, true, false);
|
||||
}
|
||||
clearTo10(count: number) {
|
||||
this.clearAll(count, true, false, true, true);
|
||||
}
|
||||
clearAll(count: number, andHigher: boolean, old: boolean, singles: boolean, trim: boolean) {
|
||||
this.pending = true;
|
||||
|
||||
return this.model.clearOrigins(count, andHigher, { old, singles, trim })
|
||||
.finally(() => {
|
||||
this.pending = false;
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
return this.model.clearOrigins(count, andHigher, { old, singles, trim })
|
||||
.finally(() => {
|
||||
this.pending = false;
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,85 +6,85 @@ import { Subscription } from '../../../common/interfaces';
|
||||
import { showTextInNewTab } from '../../../client/htmlUtils';
|
||||
|
||||
interface Field {
|
||||
key: keyof GeneralSettings;
|
||||
title: string;
|
||||
value: string | undefined;
|
||||
key: keyof GeneralSettings;
|
||||
title: string;
|
||||
value: string | undefined;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'admin-other',
|
||||
templateUrl: 'admin-other.pug',
|
||||
selector: 'admin-other',
|
||||
templateUrl: 'admin-other.pug',
|
||||
})
|
||||
export class AdminOther implements OnInit, OnDestroy {
|
||||
fields: Field[] = [
|
||||
{ key: 'suspiciousNames', title: 'Suspicious pony names & emails to report', value: undefined },
|
||||
{ key: 'suspiciousAuths', title: 'Suspicious auths to report', value: undefined },
|
||||
{ key: 'suspiciousMessages', title: 'Suspicious messages to report (instant)', value: undefined },
|
||||
{ key: 'suspiciousSafeMessages', title: 'Suspicious messages to report (safe only) (5+)', value: undefined },
|
||||
{ key: 'suspiciousSafeWholeMessages', title: 'Suspicious messages to report (safe only) (5+) (whole words)', value: undefined },
|
||||
{ key: 'suspiciousSafeInstantMessages', title: 'Suspicious messages to report (safe only) (instant)', value: undefined },
|
||||
{
|
||||
key: 'suspiciousSafeInstantWholeMessages',
|
||||
title: 'Suspicious messages to report (safe only) (whole words) (instant)',
|
||||
value: undefined
|
||||
},
|
||||
];
|
||||
max = 100;
|
||||
value = 0;
|
||||
error?: string;
|
||||
succeeded = false;
|
||||
suspiciousPonies?: string;
|
||||
suspiciousPoniesError?: string;
|
||||
ignoreErrors?: string;
|
||||
account: Account | undefined;
|
||||
private subscription?: Subscription;
|
||||
constructor(public model: AdminModel) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.model.accountPromise.then(() => {
|
||||
this.resetFields();
|
||||
this.resetSuspiciousPonies();
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
saveFields() {
|
||||
let settings: any = {};
|
||||
this.fields.forEach(field => settings[field.key] = field.value);
|
||||
this.model.updateSettings(settings);
|
||||
}
|
||||
resetFields() {
|
||||
this.fields.map(field => {
|
||||
field.value = this.model.state.loginServers[0][field.key] as string | undefined;
|
||||
});
|
||||
}
|
||||
saveSuspiciousPonies() {
|
||||
this.suspiciousPoniesError = undefined;
|
||||
fields: Field[] = [
|
||||
{ key: 'suspiciousNames', title: 'Suspicious pony names & emails to report', value: undefined },
|
||||
{ key: 'suspiciousAuths', title: 'Suspicious auths to report', value: undefined },
|
||||
{ key: 'suspiciousMessages', title: 'Suspicious messages to report (instant)', value: undefined },
|
||||
{ key: 'suspiciousSafeMessages', title: 'Suspicious messages to report (safe only) (5+)', value: undefined },
|
||||
{ key: 'suspiciousSafeWholeMessages', title: 'Suspicious messages to report (safe only) (5+) (whole words)', value: undefined },
|
||||
{ key: 'suspiciousSafeInstantMessages', title: 'Suspicious messages to report (safe only) (instant)', value: undefined },
|
||||
{
|
||||
key: 'suspiciousSafeInstantWholeMessages',
|
||||
title: 'Suspicious messages to report (safe only) (whole words) (instant)',
|
||||
value: undefined
|
||||
},
|
||||
];
|
||||
max = 100;
|
||||
value = 0;
|
||||
error?: string;
|
||||
succeeded = false;
|
||||
suspiciousPonies?: string;
|
||||
suspiciousPoniesError?: string;
|
||||
ignoreErrors?: string;
|
||||
account: Account | undefined;
|
||||
private subscription?: Subscription;
|
||||
constructor(public model: AdminModel) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.model.accountPromise.then(() => {
|
||||
this.resetFields();
|
||||
this.resetSuspiciousPonies();
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
saveFields() {
|
||||
let settings: any = {};
|
||||
this.fields.forEach(field => settings[field.key] = field.value);
|
||||
this.model.updateSettings(settings);
|
||||
}
|
||||
resetFields() {
|
||||
this.fields.map(field => {
|
||||
field.value = this.model.state.loginServers[0][field.key] as string | undefined;
|
||||
});
|
||||
}
|
||||
saveSuspiciousPonies() {
|
||||
this.suspiciousPoniesError = undefined;
|
||||
|
||||
try {
|
||||
compact(this.suspiciousPonies!.split(/\n/g).map(x => x.trim())).map(x => JSON.parse(x));
|
||||
this.model.updateSettings({ suspiciousPonies: this.suspiciousPonies });
|
||||
} catch (e) {
|
||||
this.suspiciousPoniesError = e.message;
|
||||
}
|
||||
}
|
||||
resetSuspiciousPonies() {
|
||||
this.suspiciousPonies = this.model.state.loginServers[0].suspiciousPonies;
|
||||
}
|
||||
updatePatreon() {
|
||||
this.model.server.updatePatreon();
|
||||
}
|
||||
updatePatreonToken(patreonToken: string) {
|
||||
this.model.updateSettings({ patreonToken });
|
||||
}
|
||||
getLastPatreonData() {
|
||||
this.model.getLastPatreonData()
|
||||
.then(data => {
|
||||
showTextInNewTab(JSON.stringify(data, null, 2));
|
||||
});
|
||||
}
|
||||
updatePastSupporters() {
|
||||
this.model.updatePastSupporters();
|
||||
}
|
||||
try {
|
||||
compact(this.suspiciousPonies!.split(/\n/g).map(x => x.trim())).map(x => JSON.parse(x));
|
||||
this.model.updateSettings({ suspiciousPonies: this.suspiciousPonies });
|
||||
} catch (e) {
|
||||
this.suspiciousPoniesError = e.message;
|
||||
}
|
||||
}
|
||||
resetSuspiciousPonies() {
|
||||
this.suspiciousPonies = this.model.state.loginServers[0].suspiciousPonies;
|
||||
}
|
||||
updatePatreon() {
|
||||
this.model.server.updatePatreon();
|
||||
}
|
||||
updatePatreonToken(patreonToken: string) {
|
||||
this.model.updateSettings({ patreonToken });
|
||||
}
|
||||
getLastPatreonData() {
|
||||
this.model.getLastPatreonData()
|
||||
.then(data => {
|
||||
showTextInNewTab(JSON.stringify(data, null, 2));
|
||||
});
|
||||
}
|
||||
updatePastSupporters() {
|
||||
this.model.updatePastSupporters();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,89 +9,89 @@ let currentPage = 1;
|
||||
let query: FindPonyQuery = {};
|
||||
|
||||
@Component({
|
||||
selector: 'admin-ponies',
|
||||
templateUrl: 'admin-ponies.pug',
|
||||
styleUrls: ['admin-ponies.scss'],
|
||||
selector: 'admin-ponies',
|
||||
templateUrl: 'admin-ponies.pug',
|
||||
styleUrls: ['admin-ponies.scss'],
|
||||
})
|
||||
export class AdminPonies implements OnInit {
|
||||
readonly syncIcon = faSync;
|
||||
readonly filterIcon = faFilter;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly commentIcon = faComment;
|
||||
items?: string[];
|
||||
itemsPerPage = 20;
|
||||
query: FindPonyQuery = {};
|
||||
loading = false;
|
||||
private lastQuery: FindPonyQuery = {};
|
||||
private totalCount?: number;
|
||||
private execSearch = debounce(() => this.fetchPonies(false), 1000);
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get totalItems() {
|
||||
return this.totalCount === undefined ? this.model.counts.characters : this.totalCount;
|
||||
}
|
||||
get search() {
|
||||
return query.search;
|
||||
}
|
||||
set search(value: string | undefined) {
|
||||
if (query.search !== value) {
|
||||
query.search = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
get orderBy() {
|
||||
return query.orderBy;
|
||||
}
|
||||
set orderBy(value: string | undefined) {
|
||||
if (query.orderBy !== value) {
|
||||
query.orderBy = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
get currentPage() {
|
||||
return currentPage;
|
||||
}
|
||||
set currentPage(value) {
|
||||
if (currentPage !== value) {
|
||||
currentPage = value;
|
||||
this.fetchPonies(isEqual(this.lastQuery, query));
|
||||
}
|
||||
}
|
||||
ngOnInit() {
|
||||
if (this.model.connected) {
|
||||
this.fetchPonies(false);
|
||||
}
|
||||
}
|
||||
refresh() {
|
||||
this.fetchPonies(false);
|
||||
}
|
||||
remove(pony: Character) {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.removePony(pony._id)
|
||||
.then(() => delay(500))
|
||||
.then(() => this.refresh());
|
||||
}
|
||||
}
|
||||
private fetchPonies(skipTotalCount: boolean) {
|
||||
const thisQuery = cloneDeep(query);
|
||||
this.lastQuery = cloneDeep(query);
|
||||
this.loading = true;
|
||||
readonly syncIcon = faSync;
|
||||
readonly filterIcon = faFilter;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly commentIcon = faComment;
|
||||
items?: string[];
|
||||
itemsPerPage = 20;
|
||||
query: FindPonyQuery = {};
|
||||
loading = false;
|
||||
private lastQuery: FindPonyQuery = {};
|
||||
private totalCount?: number;
|
||||
private execSearch = debounce(() => this.fetchPonies(false), 1000);
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get totalItems() {
|
||||
return this.totalCount === undefined ? this.model.counts.characters : this.totalCount;
|
||||
}
|
||||
get search() {
|
||||
return query.search;
|
||||
}
|
||||
set search(value: string | undefined) {
|
||||
if (query.search !== value) {
|
||||
query.search = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
get orderBy() {
|
||||
return query.orderBy;
|
||||
}
|
||||
set orderBy(value: string | undefined) {
|
||||
if (query.orderBy !== value) {
|
||||
query.orderBy = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
get currentPage() {
|
||||
return currentPage;
|
||||
}
|
||||
set currentPage(value) {
|
||||
if (currentPage !== value) {
|
||||
currentPage = value;
|
||||
this.fetchPonies(isEqual(this.lastQuery, query));
|
||||
}
|
||||
}
|
||||
ngOnInit() {
|
||||
if (this.model.connected) {
|
||||
this.fetchPonies(false);
|
||||
}
|
||||
}
|
||||
refresh() {
|
||||
this.fetchPonies(false);
|
||||
}
|
||||
remove(pony: Character) {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.removePony(pony._id)
|
||||
.then(() => delay(500))
|
||||
.then(() => this.refresh());
|
||||
}
|
||||
}
|
||||
private fetchPonies(skipTotalCount: boolean) {
|
||||
const thisQuery = cloneDeep(query);
|
||||
this.lastQuery = cloneDeep(query);
|
||||
this.loading = true;
|
||||
|
||||
thisQuery.search = thisQuery.search && thisQuery.search.trim();
|
||||
thisQuery.search = thisQuery.search && thisQuery.search.trim();
|
||||
|
||||
this.model.findPonies(thisQuery, this.currentPage - 1, skipTotalCount)
|
||||
.then(result => {
|
||||
if (result) {
|
||||
if (!skipTotalCount) {
|
||||
this.totalCount = result.totalCount;
|
||||
}
|
||||
this.model.findPonies(thisQuery, this.currentPage - 1, skipTotalCount)
|
||||
.then(result => {
|
||||
if (result) {
|
||||
if (!skipTotalCount) {
|
||||
this.totalCount = result.totalCount;
|
||||
}
|
||||
|
||||
this.items = result.items;
|
||||
} else {
|
||||
this.items = [];
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false);
|
||||
}
|
||||
this.items = result.items;
|
||||
} else {
|
||||
this.items = [];
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,286 +6,286 @@ import { SERVER_FPS } from '../../../../common/constants';
|
||||
import { AgDragEvent } from '../../../shared/directives/agDrag';
|
||||
|
||||
interface Tooltip {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
text: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface ListingEntry {
|
||||
name: string;
|
||||
count: number;
|
||||
selfTime: number;
|
||||
totalTime: number;
|
||||
selfPercent: number;
|
||||
totalPercent: number;
|
||||
name: string;
|
||||
count: number;
|
||||
selfTime: number;
|
||||
totalTime: number;
|
||||
selfPercent: number;
|
||||
totalPercent: number;
|
||||
}
|
||||
|
||||
const frameTime = 1000 / SERVER_FPS;
|
||||
const timePadding = 10; // ms
|
||||
|
||||
@Component({
|
||||
selector: 'admin-reports-perf',
|
||||
templateUrl: 'admin-reports-perf.pug',
|
||||
selector: 'admin-reports-perf',
|
||||
templateUrl: 'admin-reports-perf.pug',
|
||||
})
|
||||
export class AdminReportsPerf {
|
||||
@ViewChild('container', { static: true }) container!: ElementRef;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
@ViewChild('tooltip', { static: true }) tooltip!: ElementRef;
|
||||
loaded = false;
|
||||
server = '';
|
||||
startTime = 0;
|
||||
endTime = 0;
|
||||
listing: ListingEntry[] = [];
|
||||
timings: TimingEntry[] = [];
|
||||
private tooltips: Tooltip[] = [];
|
||||
private startTimeFrom = 0;
|
||||
private endTimeFrom = 0;
|
||||
private frame: any = 0;
|
||||
private lastZoom = 0;
|
||||
constructor(private model: AdminModel) {
|
||||
if (DEVELOPMENT) {
|
||||
const interval = setInterval(() => {
|
||||
if (findById(this.model.state.gameServers, 'dev')) {
|
||||
clearInterval(interval);
|
||||
this.load('dev');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
get servers() {
|
||||
return this.model.state.gameServers.map(s => s.id);
|
||||
}
|
||||
async load(server: string) {
|
||||
this.server = server;
|
||||
this.timings = [];
|
||||
this.loaded = false;
|
||||
const result = await this.model.getTimings(server);
|
||||
@ViewChild('container', { static: true }) container!: ElementRef;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
@ViewChild('tooltip', { static: true }) tooltip!: ElementRef;
|
||||
loaded = false;
|
||||
server = '';
|
||||
startTime = 0;
|
||||
endTime = 0;
|
||||
listing: ListingEntry[] = [];
|
||||
timings: TimingEntry[] = [];
|
||||
private tooltips: Tooltip[] = [];
|
||||
private startTimeFrom = 0;
|
||||
private endTimeFrom = 0;
|
||||
private frame: any = 0;
|
||||
private lastZoom = 0;
|
||||
constructor(private model: AdminModel) {
|
||||
if (DEVELOPMENT) {
|
||||
const interval = setInterval(() => {
|
||||
if (findById(this.model.state.gameServers, 'dev')) {
|
||||
clearInterval(interval);
|
||||
this.load('dev');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
get servers() {
|
||||
return this.model.state.gameServers.map(s => s.id);
|
||||
}
|
||||
async load(server: string) {
|
||||
this.server = server;
|
||||
this.timings = [];
|
||||
this.loaded = false;
|
||||
const result = await this.model.getTimings(server);
|
||||
|
||||
if (result) {
|
||||
this.loaded = true;
|
||||
this.timings = result as TimingEntry[];
|
||||
this.setupZoom();
|
||||
this.recalcListing();
|
||||
}
|
||||
if (result) {
|
||||
this.loaded = true;
|
||||
this.timings = result as TimingEntry[];
|
||||
this.setupZoom();
|
||||
this.recalcListing();
|
||||
}
|
||||
|
||||
this.redraw();
|
||||
}
|
||||
mouseMove(e: MouseEvent) {
|
||||
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
|
||||
const x = e.pageX - rect.left;
|
||||
const y = e.pageY - rect.top;
|
||||
this.redraw();
|
||||
}
|
||||
mouseMove(e: MouseEvent) {
|
||||
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
|
||||
const x = e.pageX - rect.left;
|
||||
const y = e.pageY - rect.top;
|
||||
|
||||
const tooltip = this.tooltips.find(t => pointInRect(x, y, t));
|
||||
const element = this.tooltip.nativeElement as HTMLElement;
|
||||
const tooltip = this.tooltips.find(t => pointInRect(x, y, t));
|
||||
const element = this.tooltip.nativeElement as HTMLElement;
|
||||
|
||||
if (tooltip) {
|
||||
element.style.display = 'block';
|
||||
element.style.left = `${x + 10}px`;
|
||||
element.style.top = `${y + 10}px`;
|
||||
element.innerText = tooltip.text;
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}
|
||||
wheel(e: WheelEvent) {
|
||||
e.preventDefault();
|
||||
if (tooltip) {
|
||||
element.style.display = 'block';
|
||||
element.style.left = `${x + 10}px`;
|
||||
element.style.top = `${y + 10}px`;
|
||||
element.innerText = tooltip.text;
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}
|
||||
wheel(e: WheelEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
const deltaY = clamp(e.deltaY, -1, 1);
|
||||
const change = ((this.endTime - this.startTime) * 0.2 * deltaY);
|
||||
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
|
||||
const ratioFromLeft = (e.pageX - rect.left) / rect.width;
|
||||
const deltaY = clamp(e.deltaY, -1, 1);
|
||||
const change = ((this.endTime - this.startTime) * 0.2 * deltaY);
|
||||
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
|
||||
const ratioFromLeft = (e.pageX - rect.left) / rect.width;
|
||||
|
||||
this.startTime = this.startTime - change * ratioFromLeft;
|
||||
this.endTime = this.endTime + change * (1 - ratioFromLeft);
|
||||
this.startTime = this.startTime - change * ratioFromLeft;
|
||||
this.endTime = this.endTime + change * (1 - ratioFromLeft);
|
||||
|
||||
this.redraw();
|
||||
}
|
||||
setupZoom() {
|
||||
switch (this.lastZoom) {
|
||||
case 0:
|
||||
default:
|
||||
this.resetZoom();
|
||||
break;
|
||||
case 1:
|
||||
this.fitZoom();
|
||||
break;
|
||||
case 2:
|
||||
this.fullFrameZoom();
|
||||
break;
|
||||
}
|
||||
}
|
||||
resetZoom() {
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
this.startTime = firstTime - timePadding;
|
||||
this.endTime = lastTime + timePadding;
|
||||
this.redraw();
|
||||
this.lastZoom = 0;
|
||||
}
|
||||
fitZoom() {
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
const totalTime = lastTime - firstTime;
|
||||
this.startTime = firstTime - totalTime * 0.05;
|
||||
this.endTime = lastTime + totalTime * 0.05;
|
||||
this.redraw();
|
||||
this.lastZoom = 1;
|
||||
}
|
||||
fullFrameZoom() {
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = firstTime + frameTime;
|
||||
this.startTime = firstTime - 2;
|
||||
this.endTime = lastTime + 2;
|
||||
this.redraw();
|
||||
this.lastZoom = 2;
|
||||
}
|
||||
drag(e: AgDragEvent) {
|
||||
if (e.type === 'start') {
|
||||
this.startTimeFrom = this.startTime;
|
||||
this.endTimeFrom = this.endTime;
|
||||
}
|
||||
this.redraw();
|
||||
}
|
||||
setupZoom() {
|
||||
switch (this.lastZoom) {
|
||||
case 0:
|
||||
default:
|
||||
this.resetZoom();
|
||||
break;
|
||||
case 1:
|
||||
this.fitZoom();
|
||||
break;
|
||||
case 2:
|
||||
this.fullFrameZoom();
|
||||
break;
|
||||
}
|
||||
}
|
||||
resetZoom() {
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
this.startTime = firstTime - timePadding;
|
||||
this.endTime = lastTime + timePadding;
|
||||
this.redraw();
|
||||
this.lastZoom = 0;
|
||||
}
|
||||
fitZoom() {
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
const totalTime = lastTime - firstTime;
|
||||
this.startTime = firstTime - totalTime * 0.05;
|
||||
this.endTime = lastTime + totalTime * 0.05;
|
||||
this.redraw();
|
||||
this.lastZoom = 1;
|
||||
}
|
||||
fullFrameZoom() {
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = firstTime + frameTime;
|
||||
this.startTime = firstTime - 2;
|
||||
this.endTime = lastTime + 2;
|
||||
this.redraw();
|
||||
this.lastZoom = 2;
|
||||
}
|
||||
drag(e: AgDragEvent) {
|
||||
if (e.type === 'start') {
|
||||
this.startTimeFrom = this.startTime;
|
||||
this.endTimeFrom = this.endTime;
|
||||
}
|
||||
|
||||
const scale = (this.endTime - this.startTime) / this.canvas.nativeElement.width;
|
||||
this.startTime = this.startTimeFrom - e.dx * scale;
|
||||
this.endTime = this.endTimeFrom - e.dx * scale;
|
||||
this.redraw();
|
||||
}
|
||||
redraw() {
|
||||
this.frame = this.frame || requestAnimationFrame(() => this.draw());
|
||||
}
|
||||
draw() {
|
||||
this.frame = 0;
|
||||
const scale = (this.endTime - this.startTime) / this.canvas.nativeElement.width;
|
||||
this.startTime = this.startTimeFrom - e.dx * scale;
|
||||
this.endTime = this.endTimeFrom - e.dx * scale;
|
||||
this.redraw();
|
||||
}
|
||||
redraw() {
|
||||
this.frame = this.frame || requestAnimationFrame(() => this.draw());
|
||||
}
|
||||
draw() {
|
||||
this.frame = 0;
|
||||
|
||||
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
canvas.width = rect.width;
|
||||
canvas.height = 400;
|
||||
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
canvas.width = rect.width;
|
||||
canvas.height = 400;
|
||||
|
||||
const context = canvas.getContext('2d')!;
|
||||
context.fillStyle = '#222';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
const context = canvas.getContext('2d')!;
|
||||
context.fillStyle = '#222';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
this.tooltips.length = 0;
|
||||
this.tooltips.length = 0;
|
||||
|
||||
if (!this.timings.length)
|
||||
return;
|
||||
if (!this.timings.length)
|
||||
return;
|
||||
|
||||
const firstTime = this.timings[0].time;
|
||||
const startTime = this.startTime;
|
||||
const endTime = this.endTime;
|
||||
const totalTime = endTime - startTime;
|
||||
const timeScale = canvas.width / totalTime;
|
||||
const firstTime = this.timings[0].time;
|
||||
const startTime = this.startTime;
|
||||
const endTime = this.endTime;
|
||||
const totalTime = endTime - startTime;
|
||||
const timeScale = canvas.width / totalTime;
|
||||
|
||||
function timeToX(time: number) {
|
||||
return (time - startTime) * timeScale;
|
||||
}
|
||||
function timeToX(time: number) {
|
||||
return (time - startTime) * timeScale;
|
||||
}
|
||||
|
||||
function timeToXAligned(time: number) {
|
||||
return Math.floor(timeToX(time)) + 0.5;
|
||||
}
|
||||
function timeToXAligned(time: number) {
|
||||
return Math.floor(timeToX(time)) + 0.5;
|
||||
}
|
||||
|
||||
context.textBaseline = 'middle';
|
||||
context.font = 'Arial 14px normal';
|
||||
context.textBaseline = 'middle';
|
||||
context.font = 'Arial 14px normal';
|
||||
|
||||
// scale
|
||||
const scaleHeight = 20;
|
||||
// scale
|
||||
const scaleHeight = 20;
|
||||
|
||||
context.strokeStyle = '#666';
|
||||
context.beginPath();
|
||||
context.strokeStyle = '#666';
|
||||
context.beginPath();
|
||||
|
||||
for (let time = firstTime; time < endTime; time += frameTime) {
|
||||
context.moveTo(timeToXAligned(time), 0);
|
||||
context.lineTo(timeToXAligned(time), canvas.height);
|
||||
}
|
||||
for (let time = firstTime; time < endTime; time += frameTime) {
|
||||
context.moveTo(timeToXAligned(time), 0);
|
||||
context.lineTo(timeToXAligned(time), canvas.height);
|
||||
}
|
||||
|
||||
context.stroke();
|
||||
context.stroke();
|
||||
|
||||
context.strokeStyle = '#ddd';
|
||||
context.beginPath();
|
||||
context.moveTo(0, scaleHeight + 0.5);
|
||||
context.lineTo(canvas.width, scaleHeight + 0.5);
|
||||
context.stroke();
|
||||
context.strokeStyle = '#ddd';
|
||||
context.beginPath();
|
||||
context.moveTo(0, scaleHeight + 0.5);
|
||||
context.lineTo(canvas.width, scaleHeight + 0.5);
|
||||
context.stroke();
|
||||
|
||||
// entries
|
||||
const rowHeight = 20;
|
||||
const startStack: TimingEntry[] = [];
|
||||
// entries
|
||||
const rowHeight = 20;
|
||||
const startStack: TimingEntry[] = [];
|
||||
|
||||
for (const entry of this.timings) {
|
||||
if (entry.type === TimingEntryType.Start) {
|
||||
startStack.push(entry);
|
||||
} else {
|
||||
const start = startStack.pop()!;
|
||||
const name = start.name!;
|
||||
const startX = timeToX(start.time);
|
||||
const endX = timeToX(entry.time);
|
||||
const y = scaleHeight + 2 + startStack.length * rowHeight;
|
||||
const w = endX - startX;
|
||||
let text = name;
|
||||
const time = entry.time - start.time;
|
||||
for (const entry of this.timings) {
|
||||
if (entry.type === TimingEntryType.Start) {
|
||||
startStack.push(entry);
|
||||
} else {
|
||||
const start = startStack.pop()!;
|
||||
const name = start.name!;
|
||||
const startX = timeToX(start.time);
|
||||
const endX = timeToX(entry.time);
|
||||
const y = scaleHeight + 2 + startStack.length * rowHeight;
|
||||
const w = endX - startX;
|
||||
let text = name;
|
||||
const time = entry.time - start.time;
|
||||
|
||||
if (startStack.length === 0) {
|
||||
context.fillStyle = '#efc457';
|
||||
text = `${text} (${time.toFixed(2)} ms) ${(100 * time / frameTime).toFixed(0)}%`;
|
||||
} else if (/\(\)$/.test(name)) {
|
||||
context.fillStyle = '#d4ecc6';
|
||||
} else {
|
||||
context.fillStyle = '#c6dcec';
|
||||
}
|
||||
if (startStack.length === 0) {
|
||||
context.fillStyle = '#efc457';
|
||||
text = `${text} (${time.toFixed(2)} ms) ${(100 * time / frameTime).toFixed(0)}%`;
|
||||
} else if (/\(\)$/.test(name)) {
|
||||
context.fillStyle = '#d4ecc6';
|
||||
} else {
|
||||
context.fillStyle = '#c6dcec';
|
||||
}
|
||||
|
||||
context.fillRect(startX, y, w, rowHeight - 1);
|
||||
this.tooltips.push({ x: startX, y, w, h: rowHeight, text: `${name}\n${time.toFixed(2)} ms` });
|
||||
context.fillRect(startX, y, w, rowHeight - 1);
|
||||
this.tooltips.push({ x: startX, y, w, h: rowHeight, text: `${name}\n${time.toFixed(2)} ms` });
|
||||
|
||||
if (w > 4) {
|
||||
context.fillStyle = '#222';
|
||||
context.fillText(text, startX + 4, y + rowHeight / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recalcListing() {
|
||||
interface Entry extends TimingEntry {
|
||||
excludedTime: number;
|
||||
}
|
||||
if (w > 4) {
|
||||
context.fillStyle = '#222';
|
||||
context.fillText(text, startX + 4, y + rowHeight / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recalcListing() {
|
||||
interface Entry extends TimingEntry {
|
||||
excludedTime: number;
|
||||
}
|
||||
|
||||
this.listing = [];
|
||||
const startStack: Entry[] = [];
|
||||
this.listing = [];
|
||||
const startStack: Entry[] = [];
|
||||
|
||||
for (const entry of this.timings) {
|
||||
if (entry.type === TimingEntryType.Start) {
|
||||
startStack.push({ ...entry, excludedTime: 0 });
|
||||
} else {
|
||||
const start = startStack.pop()!;
|
||||
const name = start.name!;
|
||||
const time = entry.time - start.time;
|
||||
for (const entry of this.timings) {
|
||||
if (entry.type === TimingEntryType.Start) {
|
||||
startStack.push({ ...entry, excludedTime: 0 });
|
||||
} else {
|
||||
const start = startStack.pop()!;
|
||||
const name = start.name!;
|
||||
const time = entry.time - start.time;
|
||||
|
||||
let listing = this.listing.find(l => l.name === name);
|
||||
let listing = this.listing.find(l => l.name === name);
|
||||
|
||||
if (!listing) {
|
||||
listing = { name, selfTime: 0, totalTime: 0, selfPercent: 0, totalPercent: 0, count: 0 };
|
||||
this.listing.push(listing);
|
||||
}
|
||||
if (!listing) {
|
||||
listing = { name, selfTime: 0, totalTime: 0, selfPercent: 0, totalPercent: 0, count: 0 };
|
||||
this.listing.push(listing);
|
||||
}
|
||||
|
||||
listing.count++;
|
||||
listing.selfTime += (time - start.excludedTime);
|
||||
listing.totalTime += time;
|
||||
listing.count++;
|
||||
listing.selfTime += (time - start.excludedTime);
|
||||
listing.totalTime += time;
|
||||
|
||||
if (startStack.length) {
|
||||
startStack[startStack.length - 1].excludedTime += time;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startStack.length) {
|
||||
startStack[startStack.length - 1].excludedTime += time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
const totalTime = lastTime - firstTime;
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
const totalTime = lastTime - firstTime;
|
||||
|
||||
for (const listing of this.listing) {
|
||||
listing.selfPercent = 100 * listing.selfTime / totalTime;
|
||||
listing.totalPercent = 100 * listing.totalTime / totalTime;
|
||||
}
|
||||
for (const listing of this.listing) {
|
||||
listing.selfPercent = 100 * listing.selfTime / totalTime;
|
||||
listing.totalPercent = 100 * listing.totalTime / totalTime;
|
||||
}
|
||||
|
||||
this.listing.sort((a, b) => b.selfTime - a.selfTime);
|
||||
}
|
||||
this.listing.sort((a, b) => b.selfTime - a.selfTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'admin-reports',
|
||||
templateUrl: 'admin-reports.pug',
|
||||
selector: 'admin-reports',
|
||||
templateUrl: 'admin-reports.pug',
|
||||
})
|
||||
export class AdminReports {
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'admin-sign-in',
|
||||
templateUrl: 'admin-sign-in.pug',
|
||||
selector: 'admin-sign-in',
|
||||
templateUrl: 'admin-sign-in.pug',
|
||||
})
|
||||
export class AdminSignIn {
|
||||
}
|
||||
|
||||
@@ -1,111 +1,111 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
GameServerState, SERVER_SETTINGS, LOGIN_SERVER_SETTINGS, ServerStats, RequestStats,
|
||||
UserCountStats, Stats, StatsTable
|
||||
GameServerState, SERVER_SETTINGS, LOGIN_SERVER_SETTINGS, ServerStats, RequestStats,
|
||||
UserCountStats, Stats, StatsTable
|
||||
} from '../../../common/adminInterfaces';
|
||||
import { hasRole } from '../../../common/accountUtils';
|
||||
import { AdminModel } from '../../services/adminModel';
|
||||
import { faCog, faSlidersH } from '../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'admin-state',
|
||||
templateUrl: 'admin-state.pug',
|
||||
styleUrls: ['admin-state.scss'],
|
||||
selector: 'admin-state',
|
||||
templateUrl: 'admin-state.pug',
|
||||
styleUrls: ['admin-state.scss'],
|
||||
})
|
||||
export class AdminState {
|
||||
readonly cogIcon = faCog;
|
||||
readonly optionsIcon = faSlidersH;
|
||||
readonly loginOptions = LOGIN_SERVER_SETTINGS;
|
||||
readonly options = SERVER_SETTINGS;
|
||||
private stats = new Map<string, ServerStats>();
|
||||
private statsTables = new Map<string, StatsTable>();
|
||||
requestStats?: RequestStats[];
|
||||
userCounts?: UserCountStats[];
|
||||
showSettings: any = {};
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get state() {
|
||||
return this.model.state;
|
||||
}
|
||||
get loginServers() {
|
||||
return this.model.state.loginServers;
|
||||
}
|
||||
get servers() {
|
||||
return this.model.state.gameServers;
|
||||
}
|
||||
get isSuperadmin() {
|
||||
return hasRole(this.model.account, 'superadmin');
|
||||
}
|
||||
kickAll(server: GameServerState) {
|
||||
return this.model.kickAll(server.id);
|
||||
}
|
||||
updateLoginSetting(key: string, value: boolean) {
|
||||
return this.model.updateSettings({ [key]: value });
|
||||
}
|
||||
updateLoginSettings(state: any) {
|
||||
return this.model.updateSettings(state);
|
||||
}
|
||||
updateServerSetting(server: GameServerState, key: string, value: boolean) {
|
||||
return this.model.updateGameServerSettings(server.id, { [key]: value });
|
||||
}
|
||||
updateServerSettings(server: GameServerState, state: any) {
|
||||
return this.model.updateGameServerSettings(server.id, state);
|
||||
}
|
||||
// request stats
|
||||
fetchRequestStats() {
|
||||
this.model.getRequestStats()
|
||||
.then(stats => {
|
||||
this.requestStats = stats && stats.requests;
|
||||
this.userCounts = stats && stats.userCounts;
|
||||
});
|
||||
}
|
||||
resetRequestStats() {
|
||||
this.requestStats = undefined;
|
||||
this.userCounts = undefined;
|
||||
}
|
||||
// socket stats
|
||||
fetchStats(server: GameServerState) {
|
||||
return this.model.fetchServerStats(server.id)
|
||||
.then(stats => stats && this.stats.set(server.id, stats));
|
||||
}
|
||||
resetStats(server: GameServerState) {
|
||||
this.stats.delete(server.id);
|
||||
}
|
||||
getStats(server: GameServerState) {
|
||||
return this.stats.get(server.id);
|
||||
}
|
||||
// stats tables
|
||||
fetchCountryStats(server: GameServerState) {
|
||||
return this.fetchStatsTable(server, Stats.Country);
|
||||
}
|
||||
fetchSupportStats(server: GameServerState) {
|
||||
return this.fetchStatsTable(server, Stats.Support);
|
||||
}
|
||||
fetchMapStats(server: GameServerState) {
|
||||
return this.fetchStatsTable(server, Stats.Maps);
|
||||
}
|
||||
resetStatsTable(server: GameServerState) {
|
||||
this.statsTables.delete(server.id);
|
||||
}
|
||||
getStatsTable(server: GameServerState) {
|
||||
return this.statsTables.get(server.id);
|
||||
}
|
||||
private fetchStatsTable(server: GameServerState, stats: Stats) {
|
||||
return this.model.fetchServerStatsTable(server.id, stats)
|
||||
.then(stats => stats && this.statsTables.set(server.id, stats));
|
||||
}
|
||||
// updates
|
||||
notifyOfUpdate(server = '*') {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.notifyUpdate(server);
|
||||
}
|
||||
}
|
||||
shutdownServers(server = '*') {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.shutdownServers(server);
|
||||
}
|
||||
}
|
||||
resetUpdating(server = '*') {
|
||||
this.model.resetUpdating(server);
|
||||
}
|
||||
readonly cogIcon = faCog;
|
||||
readonly optionsIcon = faSlidersH;
|
||||
readonly loginOptions = LOGIN_SERVER_SETTINGS;
|
||||
readonly options = SERVER_SETTINGS;
|
||||
private stats = new Map<string, ServerStats>();
|
||||
private statsTables = new Map<string, StatsTable>();
|
||||
requestStats?: RequestStats[];
|
||||
userCounts?: UserCountStats[];
|
||||
showSettings: any = {};
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get state() {
|
||||
return this.model.state;
|
||||
}
|
||||
get loginServers() {
|
||||
return this.model.state.loginServers;
|
||||
}
|
||||
get servers() {
|
||||
return this.model.state.gameServers;
|
||||
}
|
||||
get isSuperadmin() {
|
||||
return hasRole(this.model.account, 'superadmin');
|
||||
}
|
||||
kickAll(server: GameServerState) {
|
||||
return this.model.kickAll(server.id);
|
||||
}
|
||||
updateLoginSetting(key: string, value: boolean) {
|
||||
return this.model.updateSettings({ [key]: value });
|
||||
}
|
||||
updateLoginSettings(state: any) {
|
||||
return this.model.updateSettings(state);
|
||||
}
|
||||
updateServerSetting(server: GameServerState, key: string, value: boolean) {
|
||||
return this.model.updateGameServerSettings(server.id, { [key]: value });
|
||||
}
|
||||
updateServerSettings(server: GameServerState, state: any) {
|
||||
return this.model.updateGameServerSettings(server.id, state);
|
||||
}
|
||||
// request stats
|
||||
fetchRequestStats() {
|
||||
this.model.getRequestStats()
|
||||
.then(stats => {
|
||||
this.requestStats = stats && stats.requests;
|
||||
this.userCounts = stats && stats.userCounts;
|
||||
});
|
||||
}
|
||||
resetRequestStats() {
|
||||
this.requestStats = undefined;
|
||||
this.userCounts = undefined;
|
||||
}
|
||||
// socket stats
|
||||
fetchStats(server: GameServerState) {
|
||||
return this.model.fetchServerStats(server.id)
|
||||
.then(stats => stats && this.stats.set(server.id, stats));
|
||||
}
|
||||
resetStats(server: GameServerState) {
|
||||
this.stats.delete(server.id);
|
||||
}
|
||||
getStats(server: GameServerState) {
|
||||
return this.stats.get(server.id);
|
||||
}
|
||||
// stats tables
|
||||
fetchCountryStats(server: GameServerState) {
|
||||
return this.fetchStatsTable(server, Stats.Country);
|
||||
}
|
||||
fetchSupportStats(server: GameServerState) {
|
||||
return this.fetchStatsTable(server, Stats.Support);
|
||||
}
|
||||
fetchMapStats(server: GameServerState) {
|
||||
return this.fetchStatsTable(server, Stats.Maps);
|
||||
}
|
||||
resetStatsTable(server: GameServerState) {
|
||||
this.statsTables.delete(server.id);
|
||||
}
|
||||
getStatsTable(server: GameServerState) {
|
||||
return this.statsTables.get(server.id);
|
||||
}
|
||||
private fetchStatsTable(server: GameServerState, stats: Stats) {
|
||||
return this.model.fetchServerStatsTable(server.id, stats)
|
||||
.then(stats => stats && this.statsTables.set(server.id, stats));
|
||||
}
|
||||
// updates
|
||||
notifyOfUpdate(server = '*') {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.notifyUpdate(server);
|
||||
}
|
||||
}
|
||||
shutdownServers(server = '*') {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.shutdownServers(server);
|
||||
}
|
||||
}
|
||||
resetUpdating(server = '*') {
|
||||
this.model.resetUpdating(server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,84 +56,84 @@ import { AdminApp } from './admin';
|
||||
import { ErrorReporter } from '../services/errorReporter';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: AdminState },
|
||||
{ path: 'sign-in', component: AdminSignIn },
|
||||
{ path: 'events', component: AdminEvents },
|
||||
{ path: 'accounts', component: AdminAccounts },
|
||||
{ path: 'accounts/:id', component: AdminAccountDetails },
|
||||
{ path: 'ponies', component: AdminPonies },
|
||||
{ path: 'origins', component: AdminOrigins },
|
||||
{ path: 'origins/:ip/:country', component: AdminOriginDetails },
|
||||
{
|
||||
path: 'reports',
|
||||
component: AdminReports,
|
||||
children: [
|
||||
{ path: '', redirectTo: 'perf', pathMatch: 'full' },
|
||||
{ path: 'perf', component: AdminReportsPerf },
|
||||
],
|
||||
},
|
||||
{ path: 'other', component: AdminOther },
|
||||
{ path: '', component: AdminState },
|
||||
{ path: 'sign-in', component: AdminSignIn },
|
||||
{ path: 'events', component: AdminEvents },
|
||||
{ path: 'accounts', component: AdminAccounts },
|
||||
{ path: 'accounts/:id', component: AdminAccountDetails },
|
||||
{ path: 'ponies', component: AdminPonies },
|
||||
{ path: 'origins', component: AdminOrigins },
|
||||
{ path: 'origins/:ip/:country', component: AdminOriginDetails },
|
||||
{
|
||||
path: 'reports',
|
||||
component: AdminReports,
|
||||
children: [
|
||||
{ path: '', redirectTo: 'perf', pathMatch: 'full' },
|
||||
{ path: 'perf', component: AdminReportsPerf },
|
||||
],
|
||||
},
|
||||
{ path: 'other', component: AdminOther },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
RouterModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
PopoverModule.forRoot(),
|
||||
PaginationModule.forRoot(),
|
||||
ButtonsModule.forRoot(),
|
||||
ModalModule,
|
||||
TooltipModule,
|
||||
SharedModule,
|
||||
RouterModule.forRoot(routes),
|
||||
FontAwesomeModule,
|
||||
],
|
||||
declarations: [
|
||||
KeysPipe,
|
||||
OrderByPipe,
|
||||
TranslitPipe,
|
||||
EventsTable,
|
||||
AccountInfo,
|
||||
AccountInfoRemote,
|
||||
AccountStatus,
|
||||
AccountTooltip,
|
||||
OriginInfo,
|
||||
OriginInfoRemote,
|
||||
OriginListRemote,
|
||||
PonyInfo,
|
||||
PonyInfoRemote,
|
||||
PonyListRemote,
|
||||
AuthInfo,
|
||||
AuthInfoRemote,
|
||||
AuthInfoEdit,
|
||||
AuthList,
|
||||
AuthListRemote,
|
||||
OnOffSwitch,
|
||||
AdminChatLog,
|
||||
BanIcon,
|
||||
EmailList,
|
||||
FromNow,
|
||||
TimeField,
|
||||
UAInfo,
|
||||
AdminState,
|
||||
AdminEvents,
|
||||
AdminOther,
|
||||
AdminAccounts,
|
||||
AdminAccountDetails,
|
||||
AdminPonies,
|
||||
AdminReports,
|
||||
AdminReportsPerf,
|
||||
AdminOrigins,
|
||||
AdminOriginDetails,
|
||||
AdminSignIn,
|
||||
AdminApp,
|
||||
],
|
||||
providers: [
|
||||
ErrorReporter,
|
||||
],
|
||||
bootstrap: [AdminApp],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
RouterModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
PopoverModule.forRoot(),
|
||||
PaginationModule.forRoot(),
|
||||
ButtonsModule.forRoot(),
|
||||
ModalModule,
|
||||
TooltipModule,
|
||||
SharedModule,
|
||||
RouterModule.forRoot(routes),
|
||||
FontAwesomeModule,
|
||||
],
|
||||
declarations: [
|
||||
KeysPipe,
|
||||
OrderByPipe,
|
||||
TranslitPipe,
|
||||
EventsTable,
|
||||
AccountInfo,
|
||||
AccountInfoRemote,
|
||||
AccountStatus,
|
||||
AccountTooltip,
|
||||
OriginInfo,
|
||||
OriginInfoRemote,
|
||||
OriginListRemote,
|
||||
PonyInfo,
|
||||
PonyInfoRemote,
|
||||
PonyListRemote,
|
||||
AuthInfo,
|
||||
AuthInfoRemote,
|
||||
AuthInfoEdit,
|
||||
AuthList,
|
||||
AuthListRemote,
|
||||
OnOffSwitch,
|
||||
AdminChatLog,
|
||||
BanIcon,
|
||||
EmailList,
|
||||
FromNow,
|
||||
TimeField,
|
||||
UAInfo,
|
||||
AdminState,
|
||||
AdminEvents,
|
||||
AdminOther,
|
||||
AdminAccounts,
|
||||
AdminAccountDetails,
|
||||
AdminPonies,
|
||||
AdminReports,
|
||||
AdminReportsPerf,
|
||||
AdminOrigins,
|
||||
AdminOriginDetails,
|
||||
AdminSignIn,
|
||||
AdminApp,
|
||||
],
|
||||
providers: [
|
||||
ErrorReporter,
|
||||
],
|
||||
bootstrap: [AdminApp],
|
||||
})
|
||||
export class AdminAppModule {
|
||||
}
|
||||
|
||||
@@ -5,71 +5,71 @@ import { PopoverConfig } from 'ngx-bootstrap/popover';
|
||||
import { hasRole } from '../../common/accountUtils';
|
||||
import { AdminModel } from '../services/adminModel';
|
||||
import {
|
||||
faSpinner, faSlidersH, faExclamationCircle, faUsers, faHorseHead, faMapMarkerAlt, faChartPie, faCog
|
||||
faSpinner, faSlidersH, faExclamationCircle, faUsers, faHorseHead, faMapMarkerAlt, faChartPie, faCog
|
||||
} from '../../client/icons';
|
||||
|
||||
export function tooltipConfig() {
|
||||
return Object.assign(new TooltipConfig(), { container: 'body' });
|
||||
return Object.assign(new TooltipConfig(), { container: 'body' });
|
||||
}
|
||||
|
||||
export function popoverConfig() {
|
||||
return Object.assign(new PopoverConfig(), { container: 'body' });
|
||||
return Object.assign(new PopoverConfig(), { container: 'body' });
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'pony-town-app',
|
||||
templateUrl: 'admin.pug',
|
||||
styleUrls: ['admin.scss'],
|
||||
providers: [
|
||||
{ provide: TooltipConfig, useFactory: tooltipConfig },
|
||||
{ provide: PopoverConfig, useFactory: popoverConfig },
|
||||
]
|
||||
selector: 'pony-town-app',
|
||||
templateUrl: 'admin.pug',
|
||||
styleUrls: ['admin.scss'],
|
||||
providers: [
|
||||
{ provide: TooltipConfig, useFactory: tooltipConfig },
|
||||
{ provide: PopoverConfig, useFactory: popoverConfig },
|
||||
]
|
||||
})
|
||||
export class AdminApp {
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly stateIcon = faSlidersH;
|
||||
readonly eventsIcon = faExclamationCircle;
|
||||
readonly accountsIcon = faUsers;
|
||||
readonly poniesIcon = faHorseHead;
|
||||
readonly originsIcon = faMapMarkerAlt;
|
||||
readonly reportsIcon = faChartPie;
|
||||
readonly otherIcon = faCog;
|
||||
constructor(public model: AdminModel, private router: Router) {
|
||||
}
|
||||
get loading() {
|
||||
if (!this.model.initialized) {
|
||||
return 'Initializing';
|
||||
} else if (!this.model.connected) {
|
||||
return 'Connecting';
|
||||
} else if (!this.model.loaded) {
|
||||
return 'Loading';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
get clients() {
|
||||
return this.model.state.gameServers.reduce((sum, s) => sum + s.online, 0);
|
||||
}
|
||||
get events() {
|
||||
return this.model.events.length;
|
||||
}
|
||||
get accounts() {
|
||||
return this.model.counts.accounts;
|
||||
}
|
||||
get ponies() {
|
||||
return this.model.counts.characters;
|
||||
}
|
||||
get origins() {
|
||||
return this.model.counts.origins;
|
||||
}
|
||||
get isSuperadmin() {
|
||||
return hasRole(this.model.account, 'superadmin');
|
||||
}
|
||||
@HostListener('window:go-to-account', ['$event'])
|
||||
goToAccount({ detail }: CustomEvent) {
|
||||
this.router.navigate(['/accounts', detail]);
|
||||
}
|
||||
signOut() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly stateIcon = faSlidersH;
|
||||
readonly eventsIcon = faExclamationCircle;
|
||||
readonly accountsIcon = faUsers;
|
||||
readonly poniesIcon = faHorseHead;
|
||||
readonly originsIcon = faMapMarkerAlt;
|
||||
readonly reportsIcon = faChartPie;
|
||||
readonly otherIcon = faCog;
|
||||
constructor(public model: AdminModel, private router: Router) {
|
||||
}
|
||||
get loading() {
|
||||
if (!this.model.initialized) {
|
||||
return 'Initializing';
|
||||
} else if (!this.model.connected) {
|
||||
return 'Connecting';
|
||||
} else if (!this.model.loaded) {
|
||||
return 'Loading';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
get clients() {
|
||||
return this.model.state.gameServers.reduce((sum, s) => sum + s.online, 0);
|
||||
}
|
||||
get events() {
|
||||
return this.model.events.length;
|
||||
}
|
||||
get accounts() {
|
||||
return this.model.counts.accounts;
|
||||
}
|
||||
get ponies() {
|
||||
return this.model.counts.characters;
|
||||
}
|
||||
get origins() {
|
||||
return this.model.counts.origins;
|
||||
}
|
||||
get isSuperadmin() {
|
||||
return hasRole(this.model.account, 'superadmin');
|
||||
}
|
||||
@HostListener('window:go-to-account', ['$event'])
|
||||
goToAccount({ detail }: CustomEvent) {
|
||||
this.router.navigate(['/accounts', detail]);
|
||||
}
|
||||
signOut() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +1,100 @@
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export interface BaseTableState {
|
||||
search: string;
|
||||
currentPage: number;
|
||||
sortedBy: string;
|
||||
sortedAsc: boolean;
|
||||
search: string;
|
||||
currentPage: number;
|
||||
sortedBy: string;
|
||||
sortedAsc: boolean;
|
||||
}
|
||||
|
||||
export abstract class BaseTable<T> {
|
||||
itemsPerPage = 20;
|
||||
sorted: T[] = [];
|
||||
filtered: T[] = [];
|
||||
filteredOnPage: T[] = [];
|
||||
sortedBy = 'createdAt';
|
||||
sortedAsc = true;
|
||||
private _search = '';
|
||||
private _currentPage = 1;
|
||||
private execSearch = debounce(() => {
|
||||
this.updateFiltered();
|
||||
this.onChange();
|
||||
}, 500);
|
||||
get itemsFrom() {
|
||||
return (this.currentPage - 1) * this.itemsPerPage;
|
||||
}
|
||||
get items(): T[] {
|
||||
return [];
|
||||
}
|
||||
get search() {
|
||||
return this._search;
|
||||
}
|
||||
set search(value: string) {
|
||||
if (this._search !== value) {
|
||||
this._search = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
get currentPage() {
|
||||
return this._currentPage;
|
||||
}
|
||||
set currentPage(value: number) {
|
||||
if (this._currentPage !== value) {
|
||||
this._currentPage = value;
|
||||
this.updatePage();
|
||||
this.onChange();
|
||||
}
|
||||
}
|
||||
sortBy(field: string) {
|
||||
if (this.sortedBy === field) {
|
||||
this.sortedAsc = !this.sortedAsc;
|
||||
} else {
|
||||
this.sortedBy = field;
|
||||
this.sortedAsc = true;
|
||||
}
|
||||
itemsPerPage = 20;
|
||||
sorted: T[] = [];
|
||||
filtered: T[] = [];
|
||||
filteredOnPage: T[] = [];
|
||||
sortedBy = 'createdAt';
|
||||
sortedAsc = true;
|
||||
private _search = '';
|
||||
private _currentPage = 1;
|
||||
private execSearch = debounce(() => {
|
||||
this.updateFiltered();
|
||||
this.onChange();
|
||||
}, 500);
|
||||
get itemsFrom() {
|
||||
return (this.currentPage - 1) * this.itemsPerPage;
|
||||
}
|
||||
get items(): T[] {
|
||||
return [];
|
||||
}
|
||||
get search() {
|
||||
return this._search;
|
||||
}
|
||||
set search(value: string) {
|
||||
if (this._search !== value) {
|
||||
this._search = value;
|
||||
this.execSearch();
|
||||
}
|
||||
}
|
||||
get currentPage() {
|
||||
return this._currentPage;
|
||||
}
|
||||
set currentPage(value: number) {
|
||||
if (this._currentPage !== value) {
|
||||
this._currentPage = value;
|
||||
this.updatePage();
|
||||
this.onChange();
|
||||
}
|
||||
}
|
||||
sortBy(field: string) {
|
||||
if (this.sortedBy === field) {
|
||||
this.sortedAsc = !this.sortedAsc;
|
||||
} else {
|
||||
this.sortedBy = field;
|
||||
this.sortedAsc = true;
|
||||
}
|
||||
|
||||
this.updateSorted();
|
||||
this.onChange();
|
||||
}
|
||||
sortedClass(field: string) {
|
||||
return this.sortedBy === field ? (this.sortedAsc ? 'sorted-asc' : 'sorted-desc') : undefined;
|
||||
}
|
||||
protected updateItems() {
|
||||
this.updateSorted();
|
||||
}
|
||||
protected updateSorted() {
|
||||
this.sorted = this.sortItems(this.items, this.sortedBy, this.sortedAsc);
|
||||
this.updateFiltered();
|
||||
}
|
||||
protected updateFiltered() {
|
||||
this.filtered = this.filterItems(this.sorted, this.search);
|
||||
this.updatePage();
|
||||
}
|
||||
protected updatePage() {
|
||||
this.filteredOnPage = this.filtered.slice(this.itemsFrom, this.itemsFrom + this.itemsPerPage);
|
||||
}
|
||||
protected sortItems(items: T[], _by: string, _asc: boolean) {
|
||||
return items;
|
||||
}
|
||||
protected filterItems(items: T[], _search: string) {
|
||||
return items;
|
||||
}
|
||||
protected onChange() {
|
||||
}
|
||||
protected getState(): BaseTableState {
|
||||
return {
|
||||
search: this.search,
|
||||
currentPage: this.currentPage,
|
||||
sortedBy: this.sortedBy,
|
||||
sortedAsc: this.sortedAsc,
|
||||
};
|
||||
}
|
||||
protected setState(state: BaseTableState | undefined) {
|
||||
if (state) {
|
||||
this._search = state.search;
|
||||
this._currentPage = state.currentPage || 1;
|
||||
this.sortedBy = state.sortedBy;
|
||||
this.sortedAsc = state.sortedAsc;
|
||||
}
|
||||
}
|
||||
this.updateSorted();
|
||||
this.onChange();
|
||||
}
|
||||
sortedClass(field: string) {
|
||||
return this.sortedBy === field ? (this.sortedAsc ? 'sorted-asc' : 'sorted-desc') : undefined;
|
||||
}
|
||||
protected updateItems() {
|
||||
this.updateSorted();
|
||||
}
|
||||
protected updateSorted() {
|
||||
this.sorted = this.sortItems(this.items, this.sortedBy, this.sortedAsc);
|
||||
this.updateFiltered();
|
||||
}
|
||||
protected updateFiltered() {
|
||||
this.filtered = this.filterItems(this.sorted, this.search);
|
||||
this.updatePage();
|
||||
}
|
||||
protected updatePage() {
|
||||
this.filteredOnPage = this.filtered.slice(this.itemsFrom, this.itemsFrom + this.itemsPerPage);
|
||||
}
|
||||
protected sortItems(items: T[], _by: string, _asc: boolean) {
|
||||
return items;
|
||||
}
|
||||
protected filterItems(items: T[], _search: string) {
|
||||
return items;
|
||||
}
|
||||
protected onChange() {
|
||||
}
|
||||
protected getState(): BaseTableState {
|
||||
return {
|
||||
search: this.search,
|
||||
currentPage: this.currentPage,
|
||||
sortedBy: this.sortedBy,
|
||||
sortedAsc: this.sortedAsc,
|
||||
};
|
||||
}
|
||||
protected setState(state: BaseTableState | undefined) {
|
||||
if (state) {
|
||||
this._search = state.search;
|
||||
this._currentPage = state.currentPage || 1;
|
||||
this.sortedBy = state.sortedBy;
|
||||
this.sortedAsc = state.sortedAsc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'keys',
|
||||
name: 'keys',
|
||||
})
|
||||
export class KeysPipe implements PipeTransform {
|
||||
transform(value: any) {
|
||||
return value ? Object.keys(value) : value;
|
||||
}
|
||||
transform(value: any) {
|
||||
return value ? Object.keys(value) : value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'orderBy',
|
||||
name: 'orderBy',
|
||||
})
|
||||
export class OrderByPipe implements PipeTransform {
|
||||
transform(value: any[] | undefined, compare?: any) {
|
||||
return value && value.slice().sort(compare);
|
||||
}
|
||||
transform(value: any[] | undefined, compare?: any) {
|
||||
return value && value.slice().sort(compare);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { transliterate } from 'transliteration';
|
||||
|
||||
@Pipe({
|
||||
name: 'translit',
|
||||
name: 'translit',
|
||||
})
|
||||
export class TranslitPipe implements PipeTransform {
|
||||
transform(value: string) {
|
||||
if (!value || /^[a-z0-9-_.,\[\]!@#$%^&*{}|\/\\ ]+$/i.test(value))
|
||||
return undefined;
|
||||
transform(value: string) {
|
||||
if (!value || /^[a-z0-9-_.,\[\]!@#$%^&*{}|\/\\ ]+$/i.test(value))
|
||||
return undefined;
|
||||
|
||||
const translit = transliterate(value);
|
||||
return translit !== value ? translit : undefined;
|
||||
}
|
||||
const translit = transliterate(value);
|
||||
return translit !== value ? translit : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,41 +4,41 @@ import { Subscription } from '../../../../common/interfaces';
|
||||
import { AdminModel } from '../../../services/adminModel';
|
||||
|
||||
@Component({
|
||||
selector: 'account-info-remote',
|
||||
templateUrl: 'account-info-remote.pug',
|
||||
selector: 'account-info-remote',
|
||||
templateUrl: 'account-info-remote.pug',
|
||||
})
|
||||
export class AccountInfoRemote implements OnDestroy {
|
||||
@Input() extendedAuths = false;
|
||||
@Input() popoverPlacement?: string;
|
||||
@Input() showDuplicates = false;
|
||||
@Input() basic = false;
|
||||
account?: Account;
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value) {
|
||||
if (this._accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.account = undefined;
|
||||
this.updateSubscription();
|
||||
}
|
||||
}
|
||||
private updateSubscription() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
@Input() extendedAuths = false;
|
||||
@Input() popoverPlacement?: string;
|
||||
@Input() showDuplicates = false;
|
||||
@Input() basic = false;
|
||||
account?: Account;
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value) {
|
||||
if (this._accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.account = undefined;
|
||||
this.updateSubscription();
|
||||
}
|
||||
}
|
||||
private updateSubscription() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
|
||||
if (this.accountId) {
|
||||
this.subscription = this.model.accounts
|
||||
.subscribe(this.accountId, account => this.account = account);
|
||||
}
|
||||
}
|
||||
if (this.accountId) {
|
||||
this.subscription = this.model.accounts
|
||||
.subscribe(this.accountId, account => this.account = account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import { compact } from 'lodash';
|
||||
import { DAY, MINUTE, HOUR } from '../../../../common/constants';
|
||||
import { hasFlag, fromNow, toInt, setFlag } from '../../../../common/utils';
|
||||
import {
|
||||
Account, AccountFlags, accountCounters, accountFlags, SupporterFlags, supporterFlags, BannedMuted, DuplicatesInfo
|
||||
Account, AccountFlags, accountCounters, accountFlags, SupporterFlags, supporterFlags, BannedMuted, DuplicatesInfo
|
||||
} from '../../../../common/adminInterfaces';
|
||||
import { supporterLevel, patreonSupporterLevel, getAge, isPastSupporter } from '../../../../common/adminUtils';
|
||||
import { AdminModel } from '../../../services/adminModel';
|
||||
import { AccountCounters } from '../../../../common/interfaces';
|
||||
import {
|
||||
faPatreon, faCog, faMinus, faPlus, faCheck, faFlag, faStickyNote, faCertificate, faIdBadge, faEnvelope, faFont,
|
||||
faClock, faBan, faMapMarkerAlt
|
||||
faPatreon, faCog, faMinus, faPlus, faCheck, faFlag, faStickyNote, faCertificate, faIdBadge, faEnvelope, faFont,
|
||||
faClock, faBan, faMapMarkerAlt
|
||||
} from '../../../../client/icons';
|
||||
|
||||
const EMPTY_ROLES: string[] = [];
|
||||
@@ -22,271 +22,271 @@ const accountDuplicatesIntervalTime = 10 * MINUTE;
|
||||
const accountDuplicates = new Map<string, DuplicatesInfo>();
|
||||
|
||||
const predefinedAlerts = [
|
||||
{
|
||||
name: 'erp:flagged',
|
||||
message: `Your account has been flagged for inappropriate bahavior on PG rated server. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'erp:timeout',
|
||||
message: `Your account has been timed out for inappropriate language and bahavior on PG rated server. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'dups',
|
||||
message: `Your account has been flagged for making multiple accounts. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'under',
|
||||
message: `Your account has been reported for being underage, please do NOT play on 18+ server. `
|
||||
+ `Continuing that may result in permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'erp:flagged',
|
||||
message: `Your account has been flagged for inappropriate bahavior on PG rated server. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'erp:timeout',
|
||||
message: `Your account has been timed out for inappropriate language and bahavior on PG rated server. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'dups',
|
||||
message: `Your account has been flagged for making multiple accounts. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'under',
|
||||
message: `Your account has been reported for being underage, please do NOT play on 18+ server. `
|
||||
+ `Continuing that may result in permanent ban.`
|
||||
},
|
||||
];
|
||||
|
||||
const alertExpires = [
|
||||
{ name: '1h', length: HOUR },
|
||||
{ name: '5h', length: 5 * HOUR },
|
||||
{ name: '12h', length: 12 * HOUR },
|
||||
{ name: '1d', length: DAY },
|
||||
{ name: '2d', length: 2 * DAY },
|
||||
{ name: '5d', length: 5 * DAY },
|
||||
{ name: '7d', length: 7 * DAY },
|
||||
{ name: '2w', length: 14 * DAY },
|
||||
{ name: '1h', length: HOUR },
|
||||
{ name: '5h', length: 5 * HOUR },
|
||||
{ name: '12h', length: 12 * HOUR },
|
||||
{ name: '1d', length: DAY },
|
||||
{ name: '2d', length: 2 * DAY },
|
||||
{ name: '5d', length: 5 * DAY },
|
||||
{ name: '7d', length: 7 * DAY },
|
||||
{ name: '2w', length: 14 * DAY },
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'account-info',
|
||||
templateUrl: 'account-info.pug',
|
||||
styleUrls: ['account-info.scss'],
|
||||
selector: 'account-info',
|
||||
templateUrl: 'account-info.pug',
|
||||
styleUrls: ['account-info.scss'],
|
||||
})
|
||||
export class AccountInfo implements OnInit, OnChanges {
|
||||
readonly counters = accountCounters;
|
||||
readonly flags = accountFlags;
|
||||
readonly supporterFlags = supporterFlags;
|
||||
readonly cogIcon = faCog;
|
||||
readonly minusIcon = faMinus;
|
||||
readonly plusIcon = faPlus;
|
||||
readonly checkIcon = faCheck;
|
||||
readonly flagIcon = faFlag;
|
||||
readonly noteIcon = faStickyNote;
|
||||
readonly newIcon = faCertificate;
|
||||
readonly duplicateBrowserIdIcon = faIdBadge;
|
||||
readonly duplicateEmailIcon = faEnvelope;
|
||||
readonly duplicateNameIcon = faFont;
|
||||
readonly duplicatePermaIcon = faBan;
|
||||
readonly teleportIcon = faMapMarkerAlt;
|
||||
predefinedAlerts = predefinedAlerts;
|
||||
alertExpires = alertExpires;
|
||||
alertExpire = alertExpires[3];
|
||||
alertMessage = '';
|
||||
@Input() account!: Account;
|
||||
@Input() extendedAuths = false;
|
||||
@Input() popoverPlacement?: string;
|
||||
@Input() showDuplicates = false;
|
||||
@ViewChild('alertModal', { static: true }) alertModal!: TemplateRef<any>;
|
||||
note?: string;
|
||||
duplicates?: DuplicatesInfo;
|
||||
private alertModalRef?: BsModalRef;
|
||||
private _isNoteOpen = false;
|
||||
constructor(private model: AdminModel, private modalService: BsModalService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.updateDuplicates();
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.account) {
|
||||
this.updateDuplicates();
|
||||
}
|
||||
}
|
||||
get age() {
|
||||
return this.account.birthdate ? getAge(this.account.birthdate) : '-';
|
||||
}
|
||||
get alert() {
|
||||
const alert = this.account.alert;
|
||||
return (alert && alert.expires.getTime() > Date.now()) ? alert : undefined;
|
||||
}
|
||||
get isNew() {
|
||||
return this.account.createdAt && this.account.createdAt.getTime() > newTime;
|
||||
}
|
||||
get isNoteOpen() {
|
||||
return this._isNoteOpen;
|
||||
}
|
||||
set isNoteOpen(value: boolean) {
|
||||
if (this._isNoteOpen !== value) {
|
||||
this._isNoteOpen = value;
|
||||
readonly counters = accountCounters;
|
||||
readonly flags = accountFlags;
|
||||
readonly supporterFlags = supporterFlags;
|
||||
readonly cogIcon = faCog;
|
||||
readonly minusIcon = faMinus;
|
||||
readonly plusIcon = faPlus;
|
||||
readonly checkIcon = faCheck;
|
||||
readonly flagIcon = faFlag;
|
||||
readonly noteIcon = faStickyNote;
|
||||
readonly newIcon = faCertificate;
|
||||
readonly duplicateBrowserIdIcon = faIdBadge;
|
||||
readonly duplicateEmailIcon = faEnvelope;
|
||||
readonly duplicateNameIcon = faFont;
|
||||
readonly duplicatePermaIcon = faBan;
|
||||
readonly teleportIcon = faMapMarkerAlt;
|
||||
predefinedAlerts = predefinedAlerts;
|
||||
alertExpires = alertExpires;
|
||||
alertExpire = alertExpires[3];
|
||||
alertMessage = '';
|
||||
@Input() account!: Account;
|
||||
@Input() extendedAuths = false;
|
||||
@Input() popoverPlacement?: string;
|
||||
@Input() showDuplicates = false;
|
||||
@ViewChild('alertModal', { static: true }) alertModal!: TemplateRef<any>;
|
||||
note?: string;
|
||||
duplicates?: DuplicatesInfo;
|
||||
private alertModalRef?: BsModalRef;
|
||||
private _isNoteOpen = false;
|
||||
constructor(private model: AdminModel, private modalService: BsModalService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.updateDuplicates();
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.account) {
|
||||
this.updateDuplicates();
|
||||
}
|
||||
}
|
||||
get age() {
|
||||
return this.account.birthdate ? getAge(this.account.birthdate) : '-';
|
||||
}
|
||||
get alert() {
|
||||
const alert = this.account.alert;
|
||||
return (alert && alert.expires.getTime() > Date.now()) ? alert : undefined;
|
||||
}
|
||||
get isNew() {
|
||||
return this.account.createdAt && this.account.createdAt.getTime() > newTime;
|
||||
}
|
||||
get isNoteOpen() {
|
||||
return this._isNoteOpen;
|
||||
}
|
||||
set isNoteOpen(value: boolean) {
|
||||
if (this._isNoteOpen !== value) {
|
||||
this._isNoteOpen = value;
|
||||
|
||||
if (value) {
|
||||
this.note = this.account.note;
|
||||
}
|
||||
}
|
||||
}
|
||||
get isInactive() {
|
||||
return this.account && this.account.lastVisit && this.account.lastVisit.getTime() < oldTime;
|
||||
}
|
||||
get roles() {
|
||||
return this.account.roles ? this.account.roles.filter(r => r !== 'superadmin') : EMPTY_ROLES;
|
||||
}
|
||||
get flagClass() {
|
||||
const counters = this.account.counters;
|
||||
if (value) {
|
||||
this.note = this.account.note;
|
||||
}
|
||||
}
|
||||
}
|
||||
get isInactive() {
|
||||
return this.account && this.account.lastVisit && this.account.lastVisit.getTime() < oldTime;
|
||||
}
|
||||
get roles() {
|
||||
return this.account.roles ? this.account.roles.filter(r => r !== 'superadmin') : EMPTY_ROLES;
|
||||
}
|
||||
get flagClass() {
|
||||
const counters = this.account.counters;
|
||||
|
||||
if (this.account.flags) {
|
||||
return 'text-banned';
|
||||
} else if (!counters && !this.account.supporter) {
|
||||
return 'text-muted';
|
||||
} else if (counters && ((counters.spam || 0) > 100 || (counters.swears || 0) > 10)) {
|
||||
return 'text-alert';
|
||||
} else {
|
||||
return 'text-present';
|
||||
}
|
||||
}
|
||||
get hasDuplicateNote() {
|
||||
return this.account.note && /duplicate/i.test(this.account.note);
|
||||
}
|
||||
hasFlag(value: AccountFlags) {
|
||||
return hasFlag(this.account.flags, value);
|
||||
}
|
||||
toggleFlag(value: AccountFlags) {
|
||||
this.model.setAccountFlags(this.account._id, this.account.flags ^ value);
|
||||
}
|
||||
toggleBan(field: keyof BannedMuted, value: number) {
|
||||
this.model.setAccountBanField(this.account._id, field, value);
|
||||
}
|
||||
kick() {
|
||||
this.model.kick(this.account._id);
|
||||
}
|
||||
report() {
|
||||
this.model.report(this.account._id);
|
||||
}
|
||||
blur() {
|
||||
this.model.setNote(this.account._id, this.note || '');
|
||||
this.isNoteOpen = false;
|
||||
}
|
||||
decrementCounter(name: keyof AccountCounters) {
|
||||
if (this.getCounter(name) > 0) {
|
||||
this.setCounter(name, this.getCounter(name) - 1);
|
||||
}
|
||||
}
|
||||
incrementCounter(name: keyof AccountCounters) {
|
||||
this.setCounter(name, this.getCounter(name) + 1);
|
||||
}
|
||||
getCounter(name: keyof AccountCounters) {
|
||||
const counters = this.account.counters;
|
||||
return toInt(counters && counters[name]);
|
||||
}
|
||||
setCounter(name: keyof AccountCounters, value: number) {
|
||||
const counters = this.account.counters || (this.account.counters = {});
|
||||
counters[name] = value;
|
||||
this.model.setAccountCounter(this.account._id, name, value);
|
||||
}
|
||||
removeAlert() {
|
||||
this.model.setAlert(this.account._id, '', 0);
|
||||
}
|
||||
setAlert() {
|
||||
this.alertMessage = this.alert ? this.alert.message : '';
|
||||
this.alertExpire = this.alertExpires[2];
|
||||
this.alertModalRef = this.modalService.show(this.alertModal, { ignoreBackdropClick: true });
|
||||
}
|
||||
cancelAlert() {
|
||||
this.alertModalRef && this.alertModalRef.hide();
|
||||
this.alertModalRef = undefined;
|
||||
}
|
||||
confirmAlert() {
|
||||
this.model.setAlert(this.account._id, this.alertMessage, this.alertExpire.length);
|
||||
this.cancelAlert();
|
||||
}
|
||||
private updateDuplicates() {
|
||||
const account = this.account;
|
||||
if (this.account.flags) {
|
||||
return 'text-banned';
|
||||
} else if (!counters && !this.account.supporter) {
|
||||
return 'text-muted';
|
||||
} else if (counters && ((counters.spam || 0) > 100 || (counters.swears || 0) > 10)) {
|
||||
return 'text-alert';
|
||||
} else {
|
||||
return 'text-present';
|
||||
}
|
||||
}
|
||||
get hasDuplicateNote() {
|
||||
return this.account.note && /duplicate/i.test(this.account.note);
|
||||
}
|
||||
hasFlag(value: AccountFlags) {
|
||||
return hasFlag(this.account.flags, value);
|
||||
}
|
||||
toggleFlag(value: AccountFlags) {
|
||||
this.model.setAccountFlags(this.account._id, this.account.flags ^ value);
|
||||
}
|
||||
toggleBan(field: keyof BannedMuted, value: number) {
|
||||
this.model.setAccountBanField(this.account._id, field, value);
|
||||
}
|
||||
kick() {
|
||||
this.model.kick(this.account._id);
|
||||
}
|
||||
report() {
|
||||
this.model.report(this.account._id);
|
||||
}
|
||||
blur() {
|
||||
this.model.setNote(this.account._id, this.note || '');
|
||||
this.isNoteOpen = false;
|
||||
}
|
||||
decrementCounter(name: keyof AccountCounters) {
|
||||
if (this.getCounter(name) > 0) {
|
||||
this.setCounter(name, this.getCounter(name) - 1);
|
||||
}
|
||||
}
|
||||
incrementCounter(name: keyof AccountCounters) {
|
||||
this.setCounter(name, this.getCounter(name) + 1);
|
||||
}
|
||||
getCounter(name: keyof AccountCounters) {
|
||||
const counters = this.account.counters;
|
||||
return toInt(counters && counters[name]);
|
||||
}
|
||||
setCounter(name: keyof AccountCounters, value: number) {
|
||||
const counters = this.account.counters || (this.account.counters = {});
|
||||
counters[name] = value;
|
||||
this.model.setAccountCounter(this.account._id, name, value);
|
||||
}
|
||||
removeAlert() {
|
||||
this.model.setAlert(this.account._id, '', 0);
|
||||
}
|
||||
setAlert() {
|
||||
this.alertMessage = this.alert ? this.alert.message : '';
|
||||
this.alertExpire = this.alertExpires[2];
|
||||
this.alertModalRef = this.modalService.show(this.alertModal, { ignoreBackdropClick: true });
|
||||
}
|
||||
cancelAlert() {
|
||||
this.alertModalRef && this.alertModalRef.hide();
|
||||
this.alertModalRef = undefined;
|
||||
}
|
||||
confirmAlert() {
|
||||
this.model.setAlert(this.account._id, this.alertMessage, this.alertExpire.length);
|
||||
this.cancelAlert();
|
||||
}
|
||||
private updateDuplicates() {
|
||||
const account = this.account;
|
||||
|
||||
if (this.showDuplicates && account) {
|
||||
const cached = accountDuplicates.get(account._id);
|
||||
const threshold = fromNow(-accountDuplicatesIntervalTime);
|
||||
if (this.showDuplicates && account) {
|
||||
const cached = accountDuplicates.get(account._id);
|
||||
const threshold = fromNow(-accountDuplicatesIntervalTime);
|
||||
|
||||
if (cached && cached.generatedAt > threshold.getTime()) {
|
||||
this.duplicates = cached;
|
||||
} else {
|
||||
this.model.getAllDuplicatesQuickInfo(account._id)
|
||||
.then(duplicates => {
|
||||
if (duplicates) {
|
||||
accountDuplicates.set(account._id, duplicates);
|
||||
this.duplicates = duplicates;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
teleportTo() {
|
||||
this.model.teleportTo(this.account._id);
|
||||
}
|
||||
// supporters
|
||||
get isPatreonOrSupporter() {
|
||||
return !!(this.account.patreon || this.account.supporter || this.account.supporterDeclinedSince);
|
||||
}
|
||||
get supporterClass() {
|
||||
return supporterLevel(this.account) ? 'badge-success' : 'badge-warning';
|
||||
}
|
||||
get supporterTitle() {
|
||||
const supporter = this.account.supporter!;
|
||||
const flagSupporter = (supporter & SupporterFlags.SupporterMask) !== 0;
|
||||
const patreonSupporter = patreonSupporterLevel(this.account);
|
||||
const ignorePatreon = hasFlag(supporter, SupporterFlags.IgnorePatreon);
|
||||
const pastSupporter = hasFlag(supporter, SupporterFlags.PastSupporter);
|
||||
if (cached && cached.generatedAt > threshold.getTime()) {
|
||||
this.duplicates = cached;
|
||||
} else {
|
||||
this.model.getAllDuplicatesQuickInfo(account._id)
|
||||
.then(duplicates => {
|
||||
if (duplicates) {
|
||||
accountDuplicates.set(account._id, duplicates);
|
||||
this.duplicates = duplicates;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
teleportTo() {
|
||||
this.model.teleportTo(this.account._id);
|
||||
}
|
||||
// supporters
|
||||
get isPatreonOrSupporter() {
|
||||
return !!(this.account.patreon || this.account.supporter || this.account.supporterDeclinedSince);
|
||||
}
|
||||
get supporterClass() {
|
||||
return supporterLevel(this.account) ? 'badge-success' : 'badge-warning';
|
||||
}
|
||||
get supporterTitle() {
|
||||
const supporter = this.account.supporter!;
|
||||
const flagSupporter = (supporter & SupporterFlags.SupporterMask) !== 0;
|
||||
const patreonSupporter = patreonSupporterLevel(this.account);
|
||||
const ignorePatreon = hasFlag(supporter, SupporterFlags.IgnorePatreon);
|
||||
const pastSupporter = hasFlag(supporter, SupporterFlags.PastSupporter);
|
||||
|
||||
return compact([
|
||||
flagSupporter && 'flags',
|
||||
patreonSupporter && `patreon`,
|
||||
ignorePatreon && 'ignore',
|
||||
!patreonSupporter && this.account.supporterDeclinedSince && 'declined',
|
||||
pastSupporter && 'past',
|
||||
]).join(', ');
|
||||
}
|
||||
get supporterIcon() {
|
||||
const hasPatreon = patreonSupporterLevel(this.account);
|
||||
const hasIgnoreFlag = hasFlag(this.account.supporter, SupporterFlags.IgnorePatreon);
|
||||
const hasDeclined = !!this.account.supporterDeclinedSince;
|
||||
return hasPatreon ? faPatreon : ((hasIgnoreFlag || !hasDeclined) ? faFlag : faClock);
|
||||
}
|
||||
get hasAnySupporter() {
|
||||
return (this.account.supporter! & SupporterFlags.SupporterMask) !== 0;
|
||||
}
|
||||
get hasPastSupporter() {
|
||||
return hasFlag(this.account.supporter, SupporterFlags.PastSupporter);
|
||||
}
|
||||
get supporterLevel() {
|
||||
return supporterLevel(this.account);
|
||||
}
|
||||
get supporterLevelString() {
|
||||
const level = supporterLevel(this.account);
|
||||
return level ? level : (isPastSupporter(this.account) ? 'P' : '');
|
||||
}
|
||||
isSupporter(level: number) {
|
||||
return (this.account.supporter! & SupporterFlags.SupporterMask) === level;
|
||||
}
|
||||
setSupporter(level: number) {
|
||||
const supporter = (this.account.supporter! & ~SupporterFlags.SupporterMask) | level;
|
||||
this.model.setSupporterFlags(this.account._id, supporter);
|
||||
}
|
||||
hasSupporterFlag(value: SupporterFlags) {
|
||||
return hasFlag(this.account.supporter, value);
|
||||
}
|
||||
toggleSupporterFlag(value: SupporterFlags) {
|
||||
this.model.setSupporterFlags(this.account._id, this.account.supporter! ^ value);
|
||||
}
|
||||
// past supporter
|
||||
get isForcePastSupporter() {
|
||||
return hasFlag(this.account.supporter, SupporterFlags.ForcePastSupporter);
|
||||
}
|
||||
get isIgnorePastSupporter() {
|
||||
return hasFlag(this.account.supporter, SupporterFlags.IgnorePastSupporter);
|
||||
}
|
||||
toggleForcePastSupporter() {
|
||||
const has = hasFlag(this.account.supporter, SupporterFlags.ForcePastSupporter);
|
||||
const supporter = setFlag(this.account.supporter, SupporterFlags.ForcePastSupporter, !has);
|
||||
this.model.setSupporterFlags(this.account._id, supporter);
|
||||
}
|
||||
toggleIgnorePastSupporter() {
|
||||
const has = hasFlag(this.account.supporter, SupporterFlags.IgnorePastSupporter);
|
||||
const supporter = setFlag(this.account.supporter, SupporterFlags.IgnorePastSupporter, !has);
|
||||
this.model.setSupporterFlags(this.account._id, supporter);
|
||||
}
|
||||
return compact([
|
||||
flagSupporter && 'flags',
|
||||
patreonSupporter && `patreon`,
|
||||
ignorePatreon && 'ignore',
|
||||
!patreonSupporter && this.account.supporterDeclinedSince && 'declined',
|
||||
pastSupporter && 'past',
|
||||
]).join(', ');
|
||||
}
|
||||
get supporterIcon() {
|
||||
const hasPatreon = patreonSupporterLevel(this.account);
|
||||
const hasIgnoreFlag = hasFlag(this.account.supporter, SupporterFlags.IgnorePatreon);
|
||||
const hasDeclined = !!this.account.supporterDeclinedSince;
|
||||
return hasPatreon ? faPatreon : ((hasIgnoreFlag || !hasDeclined) ? faFlag : faClock);
|
||||
}
|
||||
get hasAnySupporter() {
|
||||
return (this.account.supporter! & SupporterFlags.SupporterMask) !== 0;
|
||||
}
|
||||
get hasPastSupporter() {
|
||||
return hasFlag(this.account.supporter, SupporterFlags.PastSupporter);
|
||||
}
|
||||
get supporterLevel() {
|
||||
return supporterLevel(this.account);
|
||||
}
|
||||
get supporterLevelString() {
|
||||
const level = supporterLevel(this.account);
|
||||
return level ? level : (isPastSupporter(this.account) ? 'P' : '');
|
||||
}
|
||||
isSupporter(level: number) {
|
||||
return (this.account.supporter! & SupporterFlags.SupporterMask) === level;
|
||||
}
|
||||
setSupporter(level: number) {
|
||||
const supporter = (this.account.supporter! & ~SupporterFlags.SupporterMask) | level;
|
||||
this.model.setSupporterFlags(this.account._id, supporter);
|
||||
}
|
||||
hasSupporterFlag(value: SupporterFlags) {
|
||||
return hasFlag(this.account.supporter, value);
|
||||
}
|
||||
toggleSupporterFlag(value: SupporterFlags) {
|
||||
this.model.setSupporterFlags(this.account._id, this.account.supporter! ^ value);
|
||||
}
|
||||
// past supporter
|
||||
get isForcePastSupporter() {
|
||||
return hasFlag(this.account.supporter, SupporterFlags.ForcePastSupporter);
|
||||
}
|
||||
get isIgnorePastSupporter() {
|
||||
return hasFlag(this.account.supporter, SupporterFlags.IgnorePastSupporter);
|
||||
}
|
||||
toggleForcePastSupporter() {
|
||||
const has = hasFlag(this.account.supporter, SupporterFlags.ForcePastSupporter);
|
||||
const supporter = setFlag(this.account.supporter, SupporterFlags.ForcePastSupporter, !has);
|
||||
this.model.setSupporterFlags(this.account._id, supporter);
|
||||
}
|
||||
toggleIgnorePastSupporter() {
|
||||
const has = hasFlag(this.account.supporter, SupporterFlags.IgnorePastSupporter);
|
||||
const supporter = setFlag(this.account.supporter, SupporterFlags.IgnorePastSupporter, !has);
|
||||
this.model.setSupporterFlags(this.account._id, supporter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,25 +4,25 @@ import { Account, AccountStatus as IAccountStatus } from '../../../../common/adm
|
||||
import { faUserSecret } from '../../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'account-status',
|
||||
templateUrl: 'account-status.pug',
|
||||
selector: 'account-status',
|
||||
templateUrl: 'account-status.pug',
|
||||
})
|
||||
export class AccountStatus implements OnInit, OnChanges {
|
||||
readonly incognitoIcon = faUserSecret;
|
||||
@Input() account!: Account;
|
||||
@Input() verbose = false;
|
||||
status: IAccountStatus[] | undefined = undefined;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.refresh();
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.status = undefined;
|
||||
this.refresh();
|
||||
}
|
||||
refresh() {
|
||||
this.model.getAccountStatus(this.account._id)
|
||||
.then(status => this.status = status);
|
||||
}
|
||||
readonly incognitoIcon = faUserSecret;
|
||||
@Input() account!: Account;
|
||||
@Input() verbose = false;
|
||||
status: IAccountStatus[] | undefined = undefined;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.refresh();
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.status = undefined;
|
||||
this.refresh();
|
||||
}
|
||||
refresh() {
|
||||
this.model.getAccountStatus(this.account._id)
|
||||
.then(status => this.status = status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,16 @@ import { getAge } from '../../../../common/adminUtils';
|
||||
const year = (new Date()).getFullYear();
|
||||
|
||||
@Component({
|
||||
selector: 'account-tooltip',
|
||||
templateUrl: 'account-tooltip.pug',
|
||||
selector: 'account-tooltip',
|
||||
templateUrl: 'account-tooltip.pug',
|
||||
})
|
||||
export class AccountTooltip {
|
||||
@Input() account!: Account;
|
||||
@Input() extendedAuths = false;
|
||||
get age() {
|
||||
return this.account.birthdate ? getAge(this.account.birthdate) : '-';
|
||||
}
|
||||
get forceAge() {
|
||||
return this.account.birthyear ? (year - this.account.birthyear) : '';
|
||||
}
|
||||
@Input() account!: Account;
|
||||
@Input() extendedAuths = false;
|
||||
get age() {
|
||||
return this.account.birthdate ? getAge(this.account.birthdate) : '-';
|
||||
}
|
||||
get forceAge() {
|
||||
return this.account.birthyear ? (year - this.account.birthyear) : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,175 +8,175 @@ import { removeAllNodes, appendAllNodes, showTextInNewTab } from '../../../../cl
|
||||
import { includes } from '../../../../common/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'admin-chat-log',
|
||||
templateUrl: 'admin-chat-log.pug',
|
||||
styleUrls: ['admin-chat-log.scss'],
|
||||
selector: 'admin-chat-log',
|
||||
templateUrl: 'admin-chat-log.pug',
|
||||
styleUrls: ['admin-chat-log.scss'],
|
||||
})
|
||||
export class AdminChatLog implements OnDestroy {
|
||||
readonly searchIcon = faSearch;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly syncIcon = faSync;
|
||||
readonly fileIcon = faFileAlt;
|
||||
readonly closeIcon = faTimes;
|
||||
readonly chevronLeftIcon = faChevronLeft;
|
||||
readonly chevronRightIcon = faChevronRight;
|
||||
@Input() canClose = true;
|
||||
accounts: Account[] = [];
|
||||
search?: string;
|
||||
open = false;
|
||||
today: ChatDate = createChatDate(moment());
|
||||
dates: ChatDate[] = [/*{ value: 'all', label: 'All' },*/ ...createDateRange(new Date(), 14)];
|
||||
date?: ChatDate;
|
||||
chatRaw?: string;
|
||||
loading = false;
|
||||
private _account?: Account;
|
||||
private refreshInterval?: any;
|
||||
constructor(private model: AdminModel, private element: ElementRef) {
|
||||
}
|
||||
get autoRefresh() {
|
||||
return !!this.refreshInterval;
|
||||
}
|
||||
set autoRefresh(value: boolean) {
|
||||
if (value) {
|
||||
this.refreshInterval = this.refreshInterval || setInterval(() => this.refresh(), 10 * 1000);
|
||||
} else {
|
||||
this.stopInterval();
|
||||
}
|
||||
}
|
||||
get title() {
|
||||
return this.search || (this.account && this.account.name) || 'Chat';
|
||||
}
|
||||
get account() {
|
||||
return this._account;
|
||||
}
|
||||
@Input() set account(value) {
|
||||
const theSame = this._account === value || (value && this._account && value._id === this._account._id);
|
||||
this._account = value;
|
||||
readonly searchIcon = faSearch;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly syncIcon = faSync;
|
||||
readonly fileIcon = faFileAlt;
|
||||
readonly closeIcon = faTimes;
|
||||
readonly chevronLeftIcon = faChevronLeft;
|
||||
readonly chevronRightIcon = faChevronRight;
|
||||
@Input() canClose = true;
|
||||
accounts: Account[] = [];
|
||||
search?: string;
|
||||
open = false;
|
||||
today: ChatDate = createChatDate(moment());
|
||||
dates: ChatDate[] = [/*{ value: 'all', label: 'All' },*/ ...createDateRange(new Date(), 14)];
|
||||
date?: ChatDate;
|
||||
chatRaw?: string;
|
||||
loading = false;
|
||||
private _account?: Account;
|
||||
private refreshInterval?: any;
|
||||
constructor(private model: AdminModel, private element: ElementRef) {
|
||||
}
|
||||
get autoRefresh() {
|
||||
return !!this.refreshInterval;
|
||||
}
|
||||
set autoRefresh(value: boolean) {
|
||||
if (value) {
|
||||
this.refreshInterval = this.refreshInterval || setInterval(() => this.refresh(), 10 * 1000);
|
||||
} else {
|
||||
this.stopInterval();
|
||||
}
|
||||
}
|
||||
get title() {
|
||||
return this.search || (this.account && this.account.name) || 'Chat';
|
||||
}
|
||||
get account() {
|
||||
return this._account;
|
||||
}
|
||||
@Input() set account(value) {
|
||||
const theSame = this._account === value || (value && this._account && value._id === this._account._id);
|
||||
this._account = value;
|
||||
|
||||
if (!theSame) {
|
||||
this.date = undefined;
|
||||
this.setChatlogElements([]);
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.close();
|
||||
}
|
||||
show(account?: Account, date?: ChatDate) {
|
||||
this.search = undefined;
|
||||
this.account = account;
|
||||
this.date = date || this.today;
|
||||
this.open = true;
|
||||
this.accounts = [];
|
||||
this.refresh();
|
||||
}
|
||||
add(account: Account) {
|
||||
if (!this.account) {
|
||||
this.show(account);
|
||||
} else if (account !== this.account && !includes(this.accounts, account)) {
|
||||
this.date = this.date || this.today;
|
||||
this.accounts.push(account);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
removeAccount(index: number) {
|
||||
this.accounts.splice(index, 1);
|
||||
this.refresh();
|
||||
}
|
||||
showDate(date: ChatDate) {
|
||||
this.date = date;
|
||||
this.refresh();
|
||||
}
|
||||
prev() {
|
||||
this.switchDate(-1);
|
||||
}
|
||||
next() {
|
||||
this.switchDate(1);
|
||||
}
|
||||
all() {
|
||||
this.date = this.dates[0];
|
||||
this.refresh();
|
||||
}
|
||||
close() {
|
||||
this.account = undefined;
|
||||
this.date = undefined;
|
||||
this.open = false;
|
||||
this.setChatlogElements([]);
|
||||
this.stopInterval();
|
||||
}
|
||||
searchChat(search: string | undefined) {
|
||||
this.search = search;
|
||||
this.refresh();
|
||||
}
|
||||
refresh() {
|
||||
const date = this.date && this.date.value;
|
||||
if (!theSame) {
|
||||
this.date = undefined;
|
||||
this.setChatlogElements([]);
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.close();
|
||||
}
|
||||
show(account?: Account, date?: ChatDate) {
|
||||
this.search = undefined;
|
||||
this.account = account;
|
||||
this.date = date || this.today;
|
||||
this.open = true;
|
||||
this.accounts = [];
|
||||
this.refresh();
|
||||
}
|
||||
add(account: Account) {
|
||||
if (!this.account) {
|
||||
this.show(account);
|
||||
} else if (account !== this.account && !includes(this.accounts, account)) {
|
||||
this.date = this.date || this.today;
|
||||
this.accounts.push(account);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
removeAccount(index: number) {
|
||||
this.accounts.splice(index, 1);
|
||||
this.refresh();
|
||||
}
|
||||
showDate(date: ChatDate) {
|
||||
this.date = date;
|
||||
this.refresh();
|
||||
}
|
||||
prev() {
|
||||
this.switchDate(-1);
|
||||
}
|
||||
next() {
|
||||
this.switchDate(1);
|
||||
}
|
||||
all() {
|
||||
this.date = this.dates[0];
|
||||
this.refresh();
|
||||
}
|
||||
close() {
|
||||
this.account = undefined;
|
||||
this.date = undefined;
|
||||
this.open = false;
|
||||
this.setChatlogElements([]);
|
||||
this.stopInterval();
|
||||
}
|
||||
searchChat(search: string | undefined) {
|
||||
this.search = search;
|
||||
this.refresh();
|
||||
}
|
||||
refresh() {
|
||||
const date = this.date && this.date.value;
|
||||
|
||||
if (this.account) {
|
||||
const accounts = [this.account._id, ...this.accounts.map(a => a._id)];
|
||||
this.handleChat(this.model.accountsFormattedChat(accounts, date));
|
||||
} else if (this.search) {
|
||||
this.handleChat(this.model.searchFormattedChat(this.search, date));
|
||||
}
|
||||
}
|
||||
openLog() {
|
||||
showTextInNewTab(`${this.date ? this.date.label : 'none'}\n\n${(this.chatRaw || '').replace(/\t/g, ' ')}`);
|
||||
}
|
||||
private handleChat(promise: Promise<{ raw: string; html: HTMLElement[]; }>) {
|
||||
this.loading = true;
|
||||
if (this.account) {
|
||||
const accounts = [this.account._id, ...this.accounts.map(a => a._id)];
|
||||
this.handleChat(this.model.accountsFormattedChat(accounts, date));
|
||||
} else if (this.search) {
|
||||
this.handleChat(this.model.searchFormattedChat(this.search, date));
|
||||
}
|
||||
}
|
||||
openLog() {
|
||||
showTextInNewTab(`${this.date ? this.date.label : 'none'}\n\n${(this.chatRaw || '').replace(/\t/g, ' ')}`);
|
||||
}
|
||||
private handleChat(promise: Promise<{ raw: string; html: HTMLElement[]; }>) {
|
||||
this.loading = true;
|
||||
|
||||
promise
|
||||
.then(({ raw, html }) => {
|
||||
this.chatRaw = raw;
|
||||
this.setChatlogElements(html);
|
||||
})
|
||||
.finally(() => this.loading = false);
|
||||
}
|
||||
private switchDate(days: number) {
|
||||
const validDate = this.date && this.date.value !== 'all';
|
||||
this.date = validDate ? createChatDate(moment(this.date!.value).add(days, 'days')) : this.today;
|
||||
this.refresh();
|
||||
}
|
||||
private stopInterval() {
|
||||
clearInterval(this.refreshInterval);
|
||||
this.refreshInterval = undefined;
|
||||
}
|
||||
private getChatlogElement() {
|
||||
return (this.element.nativeElement as HTMLElement).querySelector('.chatlog');
|
||||
}
|
||||
private setChatlogElements(elements: HTMLElement[]) {
|
||||
const element = this.getChatlogElement();
|
||||
promise
|
||||
.then(({ raw, html }) => {
|
||||
this.chatRaw = raw;
|
||||
this.setChatlogElements(html);
|
||||
})
|
||||
.finally(() => this.loading = false);
|
||||
}
|
||||
private switchDate(days: number) {
|
||||
const validDate = this.date && this.date.value !== 'all';
|
||||
this.date = validDate ? createChatDate(moment(this.date!.value).add(days, 'days')) : this.today;
|
||||
this.refresh();
|
||||
}
|
||||
private stopInterval() {
|
||||
clearInterval(this.refreshInterval);
|
||||
this.refreshInterval = undefined;
|
||||
}
|
||||
private getChatlogElement() {
|
||||
return (this.element.nativeElement as HTMLElement).querySelector('.chatlog');
|
||||
}
|
||||
private setChatlogElements(elements: HTMLElement[]) {
|
||||
const element = this.getChatlogElement();
|
||||
|
||||
if (element) {
|
||||
removeAllNodes(element);
|
||||
appendAllNodes(element, elements);
|
||||
this.nodesToProcess = [
|
||||
...Array.from(element.getElementsByClassName('name')),
|
||||
...Array.from(element.getElementsByClassName('message')),
|
||||
] as HTMLElement[];
|
||||
this.atNode = 0;
|
||||
this.processNodes();
|
||||
}
|
||||
}
|
||||
private processNodes() {
|
||||
const processStep = 50;
|
||||
const nodes = this.nodesToProcess;
|
||||
if (element) {
|
||||
removeAllNodes(element);
|
||||
appendAllNodes(element, elements);
|
||||
this.nodesToProcess = [
|
||||
...Array.from(element.getElementsByClassName('name')),
|
||||
...Array.from(element.getElementsByClassName('message')),
|
||||
] as HTMLElement[];
|
||||
this.atNode = 0;
|
||||
this.processNodes();
|
||||
}
|
||||
}
|
||||
private processNodes() {
|
||||
const processStep = 50;
|
||||
const nodes = this.nodesToProcess;
|
||||
|
||||
cancelIdleCallback(this.processIdle);
|
||||
cancelIdleCallback(this.processIdle);
|
||||
|
||||
if (nodes && this.atNode < nodes.length) {
|
||||
let i = 0;
|
||||
if (nodes && this.atNode < nodes.length) {
|
||||
let i = 0;
|
||||
|
||||
while (i < processStep && (i + this.atNode) < nodes.length) {
|
||||
replaceSwears(nodes[i + this.atNode]);
|
||||
i++;
|
||||
}
|
||||
while (i < processStep && (i + this.atNode) < nodes.length) {
|
||||
replaceSwears(nodes[i + this.atNode]);
|
||||
i++;
|
||||
}
|
||||
|
||||
this.atNode += i;
|
||||
this.processIdle = requestIdleCallback(() => this.processNodes());
|
||||
} else {
|
||||
this.nodesToProcess = undefined;
|
||||
}
|
||||
}
|
||||
private nodesToProcess?: HTMLElement[];
|
||||
private atNode = 0;
|
||||
private processIdle = 0;
|
||||
this.atNode += i;
|
||||
this.processIdle = requestIdleCallback(() => this.processNodes());
|
||||
} else {
|
||||
this.nodesToProcess = undefined;
|
||||
}
|
||||
}
|
||||
private nodesToProcess?: HTMLElement[];
|
||||
private atNode = 0;
|
||||
private processIdle = 0;
|
||||
}
|
||||
|
||||
@@ -5,60 +5,60 @@ import { AdminModel } from '../../../services/adminModel';
|
||||
import { faInfo, faLock, faTrash, faEyeSlash, faArrowRight } from '../../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'auth-info-edit',
|
||||
templateUrl: 'auth-info-edit.pug',
|
||||
selector: 'auth-info-edit',
|
||||
templateUrl: 'auth-info-edit.pug',
|
||||
})
|
||||
export class AuthInfoEdit implements OnDestroy {
|
||||
readonly assignIcon = faArrowRight;
|
||||
readonly infoIcon = faInfo;
|
||||
readonly lockIcon = faLock;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly eyeSlashIcon = faEyeSlash;
|
||||
@Input() duplicates?: DuplicateResult[];
|
||||
@Input() showName = false;
|
||||
auth?: Auth;
|
||||
private _authId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get authId() {
|
||||
return this._authId;
|
||||
}
|
||||
@Input() set authId(value: string | undefined) {
|
||||
if (this.authId !== value) {
|
||||
this._authId = value;
|
||||
this.auth = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.auths.subscribe(value, auth => this.auth = auth) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
removeAuth(auth: Auth | undefined) {
|
||||
if (auth && confirm('Are you sure?')) {
|
||||
this.model.removeAuth(auth._id);
|
||||
}
|
||||
}
|
||||
showAuthData(auth: Auth | undefined) {
|
||||
if (auth) {
|
||||
this.model.getAuth(auth._id)
|
||||
.then(x => console.log(x));
|
||||
}
|
||||
}
|
||||
toggleAuthDisabled(auth: Auth | undefined) {
|
||||
if (auth) {
|
||||
this.model.updateAuth(auth._id, { disabled: !auth.disabled });
|
||||
}
|
||||
}
|
||||
toggleAuthBanned(auth: Auth | undefined) {
|
||||
if (auth) {
|
||||
this.model.updateAuth(auth._id, { banned: !auth.banned });
|
||||
}
|
||||
}
|
||||
assignTo(accountId: string) {
|
||||
if (this.authId) {
|
||||
this.model.assignAuth(this.authId, accountId);
|
||||
}
|
||||
}
|
||||
readonly assignIcon = faArrowRight;
|
||||
readonly infoIcon = faInfo;
|
||||
readonly lockIcon = faLock;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly eyeSlashIcon = faEyeSlash;
|
||||
@Input() duplicates?: DuplicateResult[];
|
||||
@Input() showName = false;
|
||||
auth?: Auth;
|
||||
private _authId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get authId() {
|
||||
return this._authId;
|
||||
}
|
||||
@Input() set authId(value: string | undefined) {
|
||||
if (this.authId !== value) {
|
||||
this._authId = value;
|
||||
this.auth = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.auths.subscribe(value, auth => this.auth = auth) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
removeAuth(auth: Auth | undefined) {
|
||||
if (auth && confirm('Are you sure?')) {
|
||||
this.model.removeAuth(auth._id);
|
||||
}
|
||||
}
|
||||
showAuthData(auth: Auth | undefined) {
|
||||
if (auth) {
|
||||
this.model.getAuth(auth._id)
|
||||
.then(x => console.log(x));
|
||||
}
|
||||
}
|
||||
toggleAuthDisabled(auth: Auth | undefined) {
|
||||
if (auth) {
|
||||
this.model.updateAuth(auth._id, { disabled: !auth.disabled });
|
||||
}
|
||||
}
|
||||
toggleAuthBanned(auth: Auth | undefined) {
|
||||
if (auth) {
|
||||
this.model.updateAuth(auth._id, { banned: !auth.banned });
|
||||
}
|
||||
}
|
||||
assignTo(accountId: string) {
|
||||
if (this.authId) {
|
||||
this.model.assignAuth(this.authId, accountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,28 +4,28 @@ import { AdminModel } from '../../../services/adminModel';
|
||||
import { Auth } from '../../../../common/adminInterfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'auth-info-remote',
|
||||
templateUrl: 'auth-info-remote.pug',
|
||||
selector: 'auth-info-remote',
|
||||
templateUrl: 'auth-info-remote.pug',
|
||||
})
|
||||
export class AuthInfoRemote implements OnDestroy {
|
||||
@Input() showName = false;
|
||||
auth?: Auth;
|
||||
private _authId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get authId() {
|
||||
return this._authId;
|
||||
}
|
||||
@Input() set authId(value: string | undefined) {
|
||||
if (this.authId !== value) {
|
||||
this._authId = value;
|
||||
this.auth = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.auths.subscribe(value, auth => this.auth = auth) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
@Input() showName = false;
|
||||
auth?: Auth;
|
||||
private _authId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get authId() {
|
||||
return this._authId;
|
||||
}
|
||||
@Input() set authId(value: string | undefined) {
|
||||
if (this.authId !== value) {
|
||||
this._authId = value;
|
||||
this.auth = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.auths.subscribe(value, auth => this.auth = auth) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,26 +3,26 @@ import { Auth } from '../../../../common/adminInterfaces';
|
||||
import { oauthIcons, faGlobe } from '../../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'auth-info',
|
||||
templateUrl: 'auth-info.pug',
|
||||
styleUrls: ['auth-info.scss'],
|
||||
host: {
|
||||
'[class.deleted]': 'deleted',
|
||||
},
|
||||
selector: 'auth-info',
|
||||
templateUrl: 'auth-info.pug',
|
||||
styleUrls: ['auth-info.scss'],
|
||||
host: {
|
||||
'[class.deleted]': 'deleted',
|
||||
},
|
||||
})
|
||||
export class AuthInfo {
|
||||
@Input() auth?: Auth;
|
||||
@Input() showName = false;
|
||||
get deleted(): boolean {
|
||||
return !!(this.auth && (this.auth.disabled || this.auth.banned));
|
||||
}
|
||||
get name(): string {
|
||||
return this.auth && this.auth.name || '';
|
||||
}
|
||||
get icon() {
|
||||
return oauthIcons[this.auth && this.auth.provider || ''] || faGlobe;
|
||||
}
|
||||
get pledged() {
|
||||
return (this.auth && this.auth.pledged || 0) / 100;
|
||||
}
|
||||
@Input() auth?: Auth;
|
||||
@Input() showName = false;
|
||||
get deleted(): boolean {
|
||||
return !!(this.auth && (this.auth.disabled || this.auth.banned));
|
||||
}
|
||||
get name(): string {
|
||||
return this.auth && this.auth.name || '';
|
||||
}
|
||||
get icon() {
|
||||
return oauthIcons[this.auth && this.auth.provider || ''] || faGlobe;
|
||||
}
|
||||
get pledged() {
|
||||
return (this.auth && this.auth.pledged || 0) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,35 +3,35 @@ import { Subscription } from '../../../../common/interfaces';
|
||||
import { AdminModel } from '../../../services/adminModel';
|
||||
|
||||
@Component({
|
||||
selector: 'auth-list-remote',
|
||||
templateUrl: 'auth-list-remote.pug',
|
||||
styleUrls: ['auth-list-remote.scss'],
|
||||
selector: 'auth-list-remote',
|
||||
templateUrl: 'auth-list-remote.pug',
|
||||
styleUrls: ['auth-list-remote.scss'],
|
||||
})
|
||||
export class AuthListRemote implements OnDestroy {
|
||||
@Input() limit = 6;
|
||||
@Input() extended = false;
|
||||
auths: string[] = [];
|
||||
loading = false;
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value: string | undefined) {
|
||||
if (this.accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.auths = [];
|
||||
this.loading = true;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.accountAuths.subscribe(value, auths => {
|
||||
this.auths = auths || [];
|
||||
this.loading = false;
|
||||
}) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
@Input() limit = 6;
|
||||
@Input() extended = false;
|
||||
auths: string[] = [];
|
||||
loading = false;
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value: string | undefined) {
|
||||
if (this.accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.auths = [];
|
||||
this.loading = true;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.accountAuths.subscribe(value, auths => {
|
||||
this.auths = auths || [];
|
||||
this.loading = false;
|
||||
}) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,18 @@ import { Component, Input } from '@angular/core';
|
||||
import { Auth } from '../../../../common/adminInterfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'auth-list',
|
||||
templateUrl: 'auth-list.pug',
|
||||
styleUrls: ['auth-list.scss'],
|
||||
host: {
|
||||
'[class.extended]': 'extended',
|
||||
},
|
||||
selector: 'auth-list',
|
||||
templateUrl: 'auth-list.pug',
|
||||
styleUrls: ['auth-list.scss'],
|
||||
host: {
|
||||
'[class.extended]': 'extended',
|
||||
},
|
||||
})
|
||||
export class AuthList {
|
||||
@Input() limit = 6;
|
||||
@Input() extended = false;
|
||||
@Input() auths?: Auth[];
|
||||
get fixedAuths() {
|
||||
return this.auths || [];
|
||||
}
|
||||
@Input() limit = 6;
|
||||
@Input() extended = false;
|
||||
@Input() auths?: Auth[];
|
||||
get fixedAuths() {
|
||||
return this.auths || [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
Component, Input, EventEmitter, Output, ChangeDetectionStrategy, OnInit, OnDestroy, OnChanges, NgZone
|
||||
Component, Input, EventEmitter, Output, ChangeDetectionStrategy, OnInit, OnDestroy, OnChanges, NgZone
|
||||
} from '@angular/core';
|
||||
import { TIMEOUTS } from '../../../../common/constants';
|
||||
import { BannedMuted } from '../../../../common/adminInterfaces';
|
||||
@@ -7,71 +7,71 @@ import { IntervalUpdateService } from '../../../services/intervalUpdateService';
|
||||
import { faClock, faMicrophoneSlash, faEyeSlash, faBan } from '../../../../client/icons';
|
||||
|
||||
const ICONS = {
|
||||
mute: faMicrophoneSlash,
|
||||
shadow: faEyeSlash,
|
||||
ban: faBan,
|
||||
mute: faMicrophoneSlash,
|
||||
shadow: faEyeSlash,
|
||||
ban: faBan,
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'ban-icon',
|
||||
templateUrl: 'ban-icon.pug',
|
||||
styleUrls: ['ban-icon.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'ban-icon',
|
||||
templateUrl: 'ban-icon.pug',
|
||||
styleUrls: ['ban-icon.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BanIcon implements OnInit, OnDestroy, OnChanges {
|
||||
readonly timeouts = TIMEOUTS;
|
||||
readonly clockIcon = faClock;
|
||||
@Input() type: keyof BannedMuted = 'ban';
|
||||
@Input() value = 0;
|
||||
@Output() toggle = new EventEmitter<number>();
|
||||
get icon() {
|
||||
return ICONS[this.type] || ICONS.ban;
|
||||
}
|
||||
get isPerma() {
|
||||
return this.value === -1;
|
||||
}
|
||||
get isTimedOut() {
|
||||
return this.value > Date.now();
|
||||
}
|
||||
get className() {
|
||||
if (this.isPerma) {
|
||||
return 'text-banned';
|
||||
} else if (this.isTimedOut) {
|
||||
return 'text-alert';
|
||||
} else {
|
||||
return 'text-muted';
|
||||
}
|
||||
}
|
||||
private timedOut = false;
|
||||
private toggleUpdate: (on: boolean) => void;
|
||||
constructor(zone: NgZone, updateService: IntervalUpdateService) {
|
||||
this.toggleUpdate = updateService.toggle(() => {
|
||||
if (this.timedOut !== this.isTimedOut) {
|
||||
zone.run(() => this.timedOut = this.isTimedOut);
|
||||
this.toggleUpdate(this.isTimedOut);
|
||||
}
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
this.toggleUpdate(this.isTimedOut);
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.toggleUpdate(this.isTimedOut);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.toggleUpdate(false);
|
||||
}
|
||||
clear() {
|
||||
this.setValue(0);
|
||||
}
|
||||
perma() {
|
||||
this.setValue(-1);
|
||||
}
|
||||
timeout(value: number) {
|
||||
this.setValue(Date.now() + value);
|
||||
}
|
||||
private setValue(value: number) {
|
||||
this.value = value;
|
||||
this.toggle.emit(value);
|
||||
}
|
||||
readonly timeouts = TIMEOUTS;
|
||||
readonly clockIcon = faClock;
|
||||
@Input() type: keyof BannedMuted = 'ban';
|
||||
@Input() value = 0;
|
||||
@Output() toggle = new EventEmitter<number>();
|
||||
get icon() {
|
||||
return ICONS[this.type] || ICONS.ban;
|
||||
}
|
||||
get isPerma() {
|
||||
return this.value === -1;
|
||||
}
|
||||
get isTimedOut() {
|
||||
return this.value > Date.now();
|
||||
}
|
||||
get className() {
|
||||
if (this.isPerma) {
|
||||
return 'text-banned';
|
||||
} else if (this.isTimedOut) {
|
||||
return 'text-alert';
|
||||
} else {
|
||||
return 'text-muted';
|
||||
}
|
||||
}
|
||||
private timedOut = false;
|
||||
private toggleUpdate: (on: boolean) => void;
|
||||
constructor(zone: NgZone, updateService: IntervalUpdateService) {
|
||||
this.toggleUpdate = updateService.toggle(() => {
|
||||
if (this.timedOut !== this.isTimedOut) {
|
||||
zone.run(() => this.timedOut = this.isTimedOut);
|
||||
this.toggleUpdate(this.isTimedOut);
|
||||
}
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
this.toggleUpdate(this.isTimedOut);
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.toggleUpdate(this.isTimedOut);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.toggleUpdate(false);
|
||||
}
|
||||
clear() {
|
||||
this.setValue(0);
|
||||
}
|
||||
perma() {
|
||||
this.setValue(-1);
|
||||
}
|
||||
timeout(value: number) {
|
||||
this.setValue(Date.now() + value);
|
||||
}
|
||||
private setValue(value: number) {
|
||||
this.value = value;
|
||||
this.toggle.emit(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'email-list',
|
||||
templateUrl: 'email-list.pug',
|
||||
selector: 'email-list',
|
||||
templateUrl: 'email-list.pug',
|
||||
})
|
||||
export class EmailList {
|
||||
@Input() emails?: string[];
|
||||
limit = 3;
|
||||
get hasMore() {
|
||||
return this.emails && this.emails.length > this.limit;
|
||||
}
|
||||
showMore() {
|
||||
this.limit = 9999;
|
||||
}
|
||||
@Input() emails?: string[];
|
||||
limit = 3;
|
||||
get hasMore() {
|
||||
return this.emails && this.emails.length > this.limit;
|
||||
}
|
||||
showMore() {
|
||||
this.limit = 9999;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,52 +5,52 @@ import { faLanguage, faTrash, faComment, faClipboard } from '../../../../client/
|
||||
import { getTranslationUrl } from '../../../../common/adminUtils';
|
||||
|
||||
@Component({
|
||||
selector: 'events-table',
|
||||
templateUrl: 'events-table.pug',
|
||||
styleUrls: ['events-table.scss'],
|
||||
selector: 'events-table',
|
||||
templateUrl: 'events-table.pug',
|
||||
styleUrls: ['events-table.scss'],
|
||||
})
|
||||
export class EventsTable {
|
||||
readonly clipboardIcon = faClipboard;
|
||||
readonly langIcon = faLanguage;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly commentIcon = faComment;
|
||||
@Input() events!: Event[];
|
||||
@Output() showChat = new EventEmitter<ChatEvent>();
|
||||
@Output() addChat = new EventEmitter<ChatEvent>();
|
||||
@Output() removedEvent = new EventEmitter<Event>();
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
serverLabel(e: Event) {
|
||||
return SERVER_LABELS[e.server] || 'badge-none';
|
||||
}
|
||||
remove(e: Event) {
|
||||
this.model.removeEvent(e._id);
|
||||
this.removedEvent.emit(e);
|
||||
}
|
||||
removeAll(e: Event) {
|
||||
this.model.events
|
||||
.filter(x => x.message === e.message)
|
||||
.forEach(x => this.model.removeEvent(x._id));
|
||||
}
|
||||
copyToNotes(e: Event, account: Account | undefined) {
|
||||
if (account) {
|
||||
const desc = e.desc ? `: ${e.desc}` : '';
|
||||
const count = e.count > 1 ? `[${e.count}] ` : '';
|
||||
const note = `${(account.note || '')}\r\n[${e.server}]${count}${e.message}${desc}`;
|
||||
this.model.setNote(account._id, note.trim());
|
||||
}
|
||||
}
|
||||
translateUrl(e: Event) {
|
||||
return getTranslationUrl(e.desc);
|
||||
}
|
||||
onShowChat(e: MouseEvent, event: Event, account: Account | undefined) {
|
||||
if (e.shiftKey) {
|
||||
this.addChat.emit({ event, account });
|
||||
} else {
|
||||
this.showChat.emit({ event, account });
|
||||
}
|
||||
}
|
||||
onAddChat(event: Event, account: Account | undefined) {
|
||||
this.addChat.emit({ event, account });
|
||||
}
|
||||
readonly clipboardIcon = faClipboard;
|
||||
readonly langIcon = faLanguage;
|
||||
readonly trashIcon = faTrash;
|
||||
readonly commentIcon = faComment;
|
||||
@Input() events!: Event[];
|
||||
@Output() showChat = new EventEmitter<ChatEvent>();
|
||||
@Output() addChat = new EventEmitter<ChatEvent>();
|
||||
@Output() removedEvent = new EventEmitter<Event>();
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
serverLabel(e: Event) {
|
||||
return SERVER_LABELS[e.server] || 'badge-none';
|
||||
}
|
||||
remove(e: Event) {
|
||||
this.model.removeEvent(e._id);
|
||||
this.removedEvent.emit(e);
|
||||
}
|
||||
removeAll(e: Event) {
|
||||
this.model.events
|
||||
.filter(x => x.message === e.message)
|
||||
.forEach(x => this.model.removeEvent(x._id));
|
||||
}
|
||||
copyToNotes(e: Event, account: Account | undefined) {
|
||||
if (account) {
|
||||
const desc = e.desc ? `: ${e.desc}` : '';
|
||||
const count = e.count > 1 ? `[${e.count}] ` : '';
|
||||
const note = `${(account.note || '')}\r\n[${e.server}]${count}${e.message}${desc}`;
|
||||
this.model.setNote(account._id, note.trim());
|
||||
}
|
||||
}
|
||||
translateUrl(e: Event) {
|
||||
return getTranslationUrl(e.desc);
|
||||
}
|
||||
onShowChat(e: MouseEvent, event: Event, account: Account | undefined) {
|
||||
if (e.shiftKey) {
|
||||
this.addChat.emit({ event, account });
|
||||
} else {
|
||||
this.showChat.emit({ event, account });
|
||||
}
|
||||
}
|
||||
onAddChat(event: Event, account: Account | undefined) {
|
||||
this.addChat.emit({ event, account });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,34 +3,34 @@ import * as moment from 'moment';
|
||||
import { IntervalUpdateService } from '../../services/intervalUpdateService';
|
||||
|
||||
@Component({
|
||||
selector: 'from-now',
|
||||
template: '<span></span>',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'from-now',
|
||||
template: '<span></span>',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FromNow implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() time?: any;
|
||||
private moment?: moment.Moment;
|
||||
private text?: string;
|
||||
private unsubscribe?: () => void;
|
||||
constructor(private element: ElementRef, private updateService: IntervalUpdateService) {
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.moment = this.time ? moment(this.time) : undefined;
|
||||
this.update();
|
||||
}
|
||||
ngOnInit() {
|
||||
this.unsubscribe = this.updateService.subscribe(() => this.update());
|
||||
this.update();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.unsubscribe && this.unsubscribe();
|
||||
}
|
||||
private update() {
|
||||
const text = this.moment ? this.moment.fromNow(true).replace('seconds', 'secs') : '';
|
||||
@Input() time?: any;
|
||||
private moment?: moment.Moment;
|
||||
private text?: string;
|
||||
private unsubscribe?: () => void;
|
||||
constructor(private element: ElementRef, private updateService: IntervalUpdateService) {
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.moment = this.time ? moment(this.time) : undefined;
|
||||
this.update();
|
||||
}
|
||||
ngOnInit() {
|
||||
this.unsubscribe = this.updateService.subscribe(() => this.update());
|
||||
this.update();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.unsubscribe && this.unsubscribe();
|
||||
}
|
||||
private update() {
|
||||
const text = this.moment ? this.moment.fromNow(true).replace('seconds', 'secs') : '';
|
||||
|
||||
if (this.text !== text) {
|
||||
this.text = text;
|
||||
(this.element.nativeElement as HTMLElement).children[0].textContent = text;
|
||||
}
|
||||
}
|
||||
if (this.text !== text) {
|
||||
this.text = text;
|
||||
(this.element.nativeElement as HTMLElement).children[0].textContent = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'on-off-switch',
|
||||
templateUrl: 'on-off-switch.pug',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'on-off-switch',
|
||||
templateUrl: 'on-off-switch.pug',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class OnOffSwitch {
|
||||
@Input() on = false;
|
||||
@Input() disabled = false;
|
||||
@Input() onText = 'ON';
|
||||
@Input() offText = 'OFF';
|
||||
@Input() label = '';
|
||||
@Output() toggle = new EventEmitter<boolean>();
|
||||
onToggle(value: boolean) {
|
||||
if (value !== this.on) {
|
||||
this.toggle.emit(value);
|
||||
}
|
||||
}
|
||||
@Input() on = false;
|
||||
@Input() disabled = false;
|
||||
@Input() onText = 'ON';
|
||||
@Input() offText = 'OFF';
|
||||
@Input() label = '';
|
||||
@Output() toggle = new EventEmitter<boolean>();
|
||||
onToggle(value: boolean) {
|
||||
if (value !== this.on) {
|
||||
this.toggle.emit(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,28 +4,28 @@ import { AdminModel } from '../../../services/adminModel';
|
||||
import { Origin } from '../../../../common/adminInterfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'origin-info-remote',
|
||||
templateUrl: 'origin-info-remote.pug',
|
||||
selector: 'origin-info-remote',
|
||||
templateUrl: 'origin-info-remote.pug',
|
||||
})
|
||||
export class OriginInfoRemote implements OnDestroy {
|
||||
@Input() showName = false;
|
||||
origin?: Origin;
|
||||
private _originIP?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get originIP() {
|
||||
return this._originIP;
|
||||
}
|
||||
@Input() set originIP(value: string | undefined) {
|
||||
if (this.originIP !== value) {
|
||||
this._originIP = value;
|
||||
this.origin = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.origins.subscribe(value, origin => this.origin = origin) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
@Input() showName = false;
|
||||
origin?: Origin;
|
||||
private _originIP?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get originIP() {
|
||||
return this._originIP;
|
||||
}
|
||||
@Input() set originIP(value: string | undefined) {
|
||||
if (this.originIP !== value) {
|
||||
this._originIP = value;
|
||||
this.origin = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.origins.subscribe(value, origin => this.origin = origin) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,20 @@ import { AdminModel } from '../../../services/adminModel';
|
||||
import { countryCodeToName } from '../../../../common/countries';
|
||||
|
||||
@Component({
|
||||
selector: 'origin-info',
|
||||
templateUrl: 'origin-info.pug',
|
||||
styleUrls: ['origin-info.scss'],
|
||||
selector: 'origin-info',
|
||||
templateUrl: 'origin-info.pug',
|
||||
styleUrls: ['origin-info.scss'],
|
||||
})
|
||||
export class OriginInfo {
|
||||
@Input() origin?: Origin;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get countryName() {
|
||||
return countryCodeToName[this.origin && this.origin.country || '??'] || 'Unknown';
|
||||
}
|
||||
toggleBan(field: keyof BannedMuted, value: number) {
|
||||
if (this.origin) {
|
||||
this.model.updateOrigin({ ip: this.origin.ip, country: this.origin.country, [field]: value });
|
||||
}
|
||||
}
|
||||
@Input() origin?: Origin;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get countryName() {
|
||||
return countryCodeToName[this.origin && this.origin.country || '??'] || 'Unknown';
|
||||
}
|
||||
toggleBan(field: keyof BannedMuted, value: number) {
|
||||
if (this.origin) {
|
||||
this.model.updateOrigin({ ip: this.origin.ip, country: this.origin.country, [field]: value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,29 @@ import { AdminModel } from '../../../services/adminModel';
|
||||
import { OriginInfoBase } from '../../../../common/adminInterfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'origin-list-remote',
|
||||
templateUrl: 'origin-list-remote.pug',
|
||||
selector: 'origin-list-remote',
|
||||
templateUrl: 'origin-list-remote.pug',
|
||||
})
|
||||
export class OriginListRemote implements OnDestroy {
|
||||
@Input() limit = 2;
|
||||
@Input() extended = false;
|
||||
origins: OriginInfoBase[] = [];
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value: string | undefined) {
|
||||
if (this.accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.origins = [];
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.accountOrigins.subscribe(value, x => this.origins = x || []) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
@Input() limit = 2;
|
||||
@Input() extended = false;
|
||||
origins: OriginInfoBase[] = [];
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value: string | undefined) {
|
||||
if (this.accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.origins = [];
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.accountOrigins.subscribe(value, x => this.origins = x || []) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,29 @@ import { AdminModel } from '../../../services/adminModel';
|
||||
import { Character } from '../../../../common/adminInterfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'pony-info-remote',
|
||||
templateUrl: 'pony-info-remote.pug',
|
||||
selector: 'pony-info-remote',
|
||||
templateUrl: 'pony-info-remote.pug',
|
||||
})
|
||||
export class PonyInfoRemote implements OnDestroy {
|
||||
@Input() highlight = false;
|
||||
@Input() showName = false;
|
||||
pony?: Character;
|
||||
private _ponyId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get ponyId() {
|
||||
return this._ponyId;
|
||||
}
|
||||
@Input() set ponyId(value: string | undefined) {
|
||||
if (this.ponyId !== value) {
|
||||
this._ponyId = value;
|
||||
this.pony = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.ponies.subscribe(value, pony => this.pony = pony) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
@Input() highlight = false;
|
||||
@Input() showName = false;
|
||||
pony?: Character;
|
||||
private _ponyId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get ponyId() {
|
||||
return this._ponyId;
|
||||
}
|
||||
@Input() set ponyId(value: string | undefined) {
|
||||
if (this.ponyId !== value) {
|
||||
this._ponyId = value;
|
||||
this.pony = undefined;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.ponies.subscribe(value, pony => this.pony = pony) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,38 +5,38 @@ import { isForbiddenName } from '../../../../common/security';
|
||||
import { AdminModel } from '../../../services/adminModel';
|
||||
|
||||
@Component({
|
||||
selector: 'pony-info',
|
||||
templateUrl: 'pony-info.pug',
|
||||
styleUrls: ['pony-info.scss'],
|
||||
selector: 'pony-info',
|
||||
templateUrl: 'pony-info.pug',
|
||||
styleUrls: ['pony-info.scss'],
|
||||
})
|
||||
export class PonyInfo implements OnChanges {
|
||||
@Input() pony?: Character;
|
||||
@Input() highlight = false;
|
||||
labelClass = 'badge-none';
|
||||
private promise?: Promise<void>;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get isBadCM() {
|
||||
return !!this.pony && hasFlag(this.pony.flags, CharacterFlags.BadCM);
|
||||
}
|
||||
ngOnChanges() {
|
||||
if (this.pony) {
|
||||
if (isForbiddenName(this.pony.name)) {
|
||||
this.labelClass = 'badge-forbidden';
|
||||
} else if (hasFlag(this.pony.flags, CharacterFlags.BadCM)) {
|
||||
this.labelClass = 'badge-danger';
|
||||
} else {
|
||||
this.labelClass = 'badge-none';
|
||||
}
|
||||
}
|
||||
}
|
||||
onShown() {
|
||||
if (this.pony && !this.pony.ponyInfo && !this.promise) {
|
||||
this.promise = this.model.getPonyInfo(this.pony)
|
||||
.finally(() => this.promise = undefined);
|
||||
}
|
||||
}
|
||||
click() {
|
||||
console.log(this.pony);
|
||||
}
|
||||
@Input() pony?: Character;
|
||||
@Input() highlight = false;
|
||||
labelClass = 'badge-none';
|
||||
private promise?: Promise<void>;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get isBadCM() {
|
||||
return !!this.pony && hasFlag(this.pony.flags, CharacterFlags.BadCM);
|
||||
}
|
||||
ngOnChanges() {
|
||||
if (this.pony) {
|
||||
if (isForbiddenName(this.pony.name)) {
|
||||
this.labelClass = 'badge-forbidden';
|
||||
} else if (hasFlag(this.pony.flags, CharacterFlags.BadCM)) {
|
||||
this.labelClass = 'badge-danger';
|
||||
} else {
|
||||
this.labelClass = 'badge-none';
|
||||
}
|
||||
}
|
||||
}
|
||||
onShown() {
|
||||
if (this.pony && !this.pony.ponyInfo && !this.promise) {
|
||||
this.promise = this.model.getPonyInfo(this.pony)
|
||||
.finally(() => this.promise = undefined);
|
||||
}
|
||||
}
|
||||
click() {
|
||||
console.log(this.pony);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,71 +5,71 @@ import { faTrash, faArrowRight } from '../../../../client/icons';
|
||||
import { Character, PonyIdDateName, DuplicateResult } from '../../../../common/adminInterfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'pony-list-remote',
|
||||
templateUrl: 'pony-list-remote.pug',
|
||||
styleUrls: ['pony-list-remote.scss'],
|
||||
selector: 'pony-list-remote',
|
||||
templateUrl: 'pony-list-remote.pug',
|
||||
styleUrls: ['pony-list-remote.scss'],
|
||||
})
|
||||
export class PonyListRemote implements OnDestroy {
|
||||
readonly trashIcon = faTrash;
|
||||
readonly assignIcon = faArrowRight;
|
||||
@Input() limit = 10;
|
||||
@Input() expanded = false;
|
||||
@Input() deletable = false;
|
||||
@Input() highlight: (pony: Character) => boolean = () => false;
|
||||
@Input() duplicates?: DuplicateResult[];
|
||||
full = false;
|
||||
ponies: string[] = [];
|
||||
loading = false;
|
||||
private ponyInfos: PonyIdDateName[] = [];
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get limitTo() {
|
||||
return this.full ? 999999 : this.limit;
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value: string | undefined) {
|
||||
if (this.accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.ponies = [];
|
||||
this.ponyInfos = [];
|
||||
this.loading = true;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.accountPonies.subscribe(value, (x = []) => {
|
||||
this.ponyInfos = x;
|
||||
this.updatePonies();
|
||||
this.loading = false;
|
||||
}) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
remove(characterId: string) {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.removePony(characterId);
|
||||
}
|
||||
}
|
||||
toggleFull() {
|
||||
this.full = !this.full;
|
||||
this.updatePonies();
|
||||
}
|
||||
assignTo(pony: string, account: string) {
|
||||
this.model.assignPony(pony, account);
|
||||
}
|
||||
private updatePonies() {
|
||||
const compare = this.full ? compareNames : compareDates;
|
||||
this.ponies = this.ponyInfos.sort(compare).map(p => p.id);
|
||||
}
|
||||
readonly trashIcon = faTrash;
|
||||
readonly assignIcon = faArrowRight;
|
||||
@Input() limit = 10;
|
||||
@Input() expanded = false;
|
||||
@Input() deletable = false;
|
||||
@Input() highlight: (pony: Character) => boolean = () => false;
|
||||
@Input() duplicates?: DuplicateResult[];
|
||||
full = false;
|
||||
ponies: string[] = [];
|
||||
loading = false;
|
||||
private ponyInfos: PonyIdDateName[] = [];
|
||||
private _accountId?: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(private model: AdminModel) {
|
||||
}
|
||||
get limitTo() {
|
||||
return this.full ? 999999 : this.limit;
|
||||
}
|
||||
get accountId() {
|
||||
return this._accountId;
|
||||
}
|
||||
@Input() set accountId(value: string | undefined) {
|
||||
if (this.accountId !== value) {
|
||||
this._accountId = value;
|
||||
this.ponies = [];
|
||||
this.ponyInfos = [];
|
||||
this.loading = true;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
this.subscription = value ? this.model.accountPonies.subscribe(value, (x = []) => {
|
||||
this.ponyInfos = x;
|
||||
this.updatePonies();
|
||||
this.loading = false;
|
||||
}) : undefined;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
remove(characterId: string) {
|
||||
if (confirm('Are you sure?')) {
|
||||
this.model.removePony(characterId);
|
||||
}
|
||||
}
|
||||
toggleFull() {
|
||||
this.full = !this.full;
|
||||
this.updatePonies();
|
||||
}
|
||||
assignTo(pony: string, account: string) {
|
||||
this.model.assignPony(pony, account);
|
||||
}
|
||||
private updatePonies() {
|
||||
const compare = this.full ? compareNames : compareDates;
|
||||
this.ponies = this.ponyInfos.sort(compare).map(p => p.id);
|
||||
}
|
||||
}
|
||||
|
||||
export function compareNames(a: { name: string }, b: { name: string }) {
|
||||
return a.name.localeCompare(b.name);
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
export function compareDates(a: { date: number }, b: { date: number }) {
|
||||
return b.date - a.date;
|
||||
return b.date - a.date;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'time-field',
|
||||
templateUrl: 'time-field.pug',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'time-field',
|
||||
templateUrl: 'time-field.pug',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TimeField {
|
||||
@Input() time: any;
|
||||
@Input() time: any;
|
||||
}
|
||||
|
||||
@@ -3,43 +3,43 @@ import { UAParser } from 'ua-parser-js';
|
||||
import { uaIcons, faQuestionCircle, faGlobe, faDesktop } from '../../../../client/icons';
|
||||
|
||||
function icon(value: string | undefined, defaultValue: any): any {
|
||||
return value && uaIcons[value] || defaultValue;
|
||||
return value && uaIcons[value] || defaultValue;
|
||||
}
|
||||
|
||||
const extensions = {
|
||||
browser: [
|
||||
[/(Amigo|YaBrowser)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION]
|
||||
],
|
||||
browser: [
|
||||
[/(Amigo|YaBrowser)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION]
|
||||
],
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'ua-info',
|
||||
templateUrl: 'ua-info.pug',
|
||||
styles: [`:host { display: inline-block; }`],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'ua-info',
|
||||
templateUrl: 'ua-info.pug',
|
||||
styles: [`:host { display: inline-block; }`],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class UAInfo {
|
||||
osClass?: string;
|
||||
osVersion?: string;
|
||||
browserClass?: string;
|
||||
browserVersion?: string;
|
||||
deviceClass?: string;
|
||||
private _userAgent?: string;
|
||||
@Input() set userAgent(value: string | undefined) {
|
||||
if (this._userAgent !== value) {
|
||||
this._userAgent = value;
|
||||
osClass?: string;
|
||||
osVersion?: string;
|
||||
browserClass?: string;
|
||||
browserVersion?: string;
|
||||
deviceClass?: string;
|
||||
private _userAgent?: string;
|
||||
@Input() set userAgent(value: string | undefined) {
|
||||
if (this._userAgent !== value) {
|
||||
this._userAgent = value;
|
||||
|
||||
const parser = new UAParser(value, extensions);
|
||||
const { os, browser, device } = parser.getResult();
|
||||
const parser = new UAParser(value, extensions);
|
||||
const { os, browser, device } = parser.getResult();
|
||||
|
||||
this.osVersion = os.version;
|
||||
this.browserVersion = (browser.version || '').replace(/\..*$/, '');
|
||||
this.osClass = icon(os.name, faQuestionCircle);
|
||||
this.browserClass = icon(browser.name, faGlobe);
|
||||
this.deviceClass = icon(device.type, faDesktop);
|
||||
}
|
||||
}
|
||||
get userAgent() {
|
||||
return this._userAgent;
|
||||
}
|
||||
this.osVersion = os.version;
|
||||
this.browserVersion = (browser.version || '').replace(/\..*$/, '');
|
||||
this.osClass = icon(os.name, faQuestionCircle);
|
||||
this.browserClass = icon(browser.name, faGlobe);
|
||||
this.deviceClass = icon(device.type, faDesktop);
|
||||
}
|
||||
}
|
||||
get userAgent() {
|
||||
return this._userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user