Pull request #961: New client dashboard

Merge in DNS/adguard-home from new-client-dashboard to master

Squashed commit of the following:

commit 7bbd67c1e3d2af62b96bf41bb356cd6b784e473e
Merge: 113743a6 9cd9054c
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Wed Feb 3 16:01:17 2021 +0300

    Merge branch 'master' into new-client-dashboard

commit 113743a60665e40383d367dc17fa709dc54e4e2e
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Wed Feb 3 15:45:16 2021 +0300

    Remove unneded modal styles

commit 04f9d93a9ac17ee046f0d5bedfb2bf5a5e6c0a48
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Wed Feb 3 14:19:56 2021 +0300

    Consider comments

commit 78a96cd8fed8b3e03547e7e45724c23db295f67b
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Mon Feb 1 18:46:52 2021 +0300

    Remove old params for MiniCssExtractPlugin

commit 40e5a9b2b1e04036deb70af17f2719eadd0c9c02
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Mon Feb 1 18:27:46 2021 +0300

    Fix mobile version

commit 509cefc308f945b03cafa62bf48257490a0a4be1
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Mon Feb 1 18:20:56 2021 +0300

    Remove unneeded imports

commit d192f39cd2503b8ec942f00ba78fca02cac9fa60
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Mon Feb 1 18:20:13 2021 +0300

    Finish first version of dashboard

commit f82429e53d334874ff7dd0641092ec83c66ab61c
Merge: fd91a0a3 3e0238aa
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Mon Feb 1 17:12:59 2021 +0300

    Merge branch 'master' into new-client-dashboard

commit fd91a0a3d76c2a052a6548232b75d151d6065b88
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Mon Feb 1 17:12:27 2021 +0300

    wip

commit 237679965052d38acfcd6a72d24b2444cc5b3896
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Fri Jan 29 11:18:10 2021 +0300

    Finish general settings

commit 397a7e10efd34a8d31bb175a5a5a7158338388d4
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Thu Jan 28 19:24:03 2021 +0300

    Add General settings page

commit 486aaa6f3f9ad66f3a0dcfcccad9a32659767e90
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Thu Jan 28 14:05:16 2021 +0300

    Remove husky

commit b895306c0655019ca56ce161e050d83b4e7f5ff1
Merge: a195f1f4 154c9c1c
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Thu Jan 28 14:03:37 2021 +0300

    Merge branch 'master' into new-client-dashboard

commit a195f1f4d46043d9c53dea08734733f9817b95a0
Merge: c45c5fe9 362f390f
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Wed Jan 27 15:46:18 2021 +0300

    Merge branch 'new-client-dashboard' of ssh://bit.adguard.com:7999/dns/adguard-home into new-client-dashboard

commit c45c5fe92e6c5c852bec8f512dc46b4cd513156c
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Wed Jan 27 15:46:01 2021 +0300

    wip

commit 362f390fd3dcfca75633a8d30a2e54c3c30b4f3d
Author: Vladislav Abdulmyanov <v.abdulmyanov@adguard.com>
Date:   Wed Jan 27 15:45:12 2021 +0300

    Pull request #949: + client: add setup guide page

    Merge in DNS/adguard-home from 2554-setup-guide to new-client-dashboard

    Squashed commit of the following:

    commit c240d52e9e5d90429f2018fde808f4d04ccec138
    Merge: 256f1056 137b88e4
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Wed Jan 27 14:13:52 2021 +0300

        Merge branch 'new-client-dashboard' into 2554-setup-guide

    commit 256f1056770c67339e93275ab6dc7aaf2c10da0b
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Wed Jan 27 14:10:45 2021 +0300

        + client: add DNS addresses to the setup guide

    commit 0ecf91275a16ecc0dca23cae2ae209836fc622d2
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Wed Jan 27 14:00:12 2021 +0300

        + client: add setup guide tabs

commit 137b88e4253af5be32d542adbe74575ef74805c8
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Thu Jan 21 19:17:58 2021 +0300

    Add clients top

commit c3318e6932d87fdff5f22d76bee12b49f099129a
Merge: 2776276b 021eb22f
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Thu Jan 21 19:15:57 2021 +0300

    Merge branch 'new-client-dashboard' of ssh://bit.adguard.com:7999/dns/adguard-home into new-client-dashboard

commit 2776276b2e6dc026e1326b02c388fcf7d48d47ff
Author: Vlad <v.abdulmyanov@adguard.com>
Date:   Thu Jan 21 19:15:53 2021 +0300

    Add top client info

commit 021eb22ff877aec12eb7fab60147a2cc2ddd08b7
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jan 21 14:13:54 2021 +0300

    Merge: client: add sidebar

    Squashed commit of the following:

    commit 6885ba953971e68602889fbb3219221f90265421
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Thu Jan 21 13:56:55 2021 +0300

        add sidebar mask

    commit f069bfe8cba2b31355e19a51ca00bf774ee9e560
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Thu Jan 21 13:03:47 2021 +0300

        fix store

    commit 77c8791002887ae022da07dc264d9010576e7bab
    Merge: d0a6eff6 ea6d54d4
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Thu Jan 21 13:01:04 2021 +0300

        Merge branch 'new-client-dashboard' into 2254-sidebar

    commit d0a6eff67fd74533d63f5d56382085e98ddbb702
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Thu Jan 21 12:47:32 2021 +0300

        client: remove unused file

    commit 9d2424477de85503fe41fa00cc1294cb0c0e7dfa
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Thu Jan 21 12:39:13 2021 +0300

        client: header

    commit 9ddea19c136f15b184caa72d7e82738f7d4f3f1f
    Merge: 797f1248 b694bb05
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Thu Jan 21 10:57:24 2021 +0300

        Merge branch 'new-client-dashboard' into 2254-sidebar

    commit 797f1248df5c1ef8e59c2a9999138f9e05a7adaa
    Author: Ildar Kamalov <ik@adguard.com>
    Date:   Thu Jan 21 10:51:57 2021 +0300

        client: sidebar

... and 14 more commits
This commit is contained in:
Vladislav Abdulmyanov
2021-02-03 16:14:20 +03:00
parent 9cd9054cdb
commit 0c127039cf
136 changed files with 5384 additions and 2938 deletions

View File

@@ -1,11 +1,32 @@
import { createContext } from 'react';
import UI from './stores/ui';
import Login from './stores/Login';
import Dashboard from './stores/Dasnboard';
import System from './stores/System';
import GeneralSettings from './stores/GeneralSettings';
export class Store {
ui: UI;
login: Login;
dashboard: Dashboard;
system: System;
generalSettings: GeneralSettings;
constructor() {
this.ui = new UI(this);
this.login = new Login(this);
this.dashboard = new Dashboard(this);
this.system = new System(this);
this.generalSettings = new GeneralSettings(this);
}
init() {
this.dashboard.init();
this.system.init();
}
}

View File

@@ -0,0 +1,120 @@
import { flow, makeAutoObservable, observable } from 'mobx';
import clientsApi from 'Apis/clients';
import statsApi from 'Apis/stats';
import filteringApi from 'Apis/filtering';
import tlsApi from 'Apis/tls';
import { errorChecker } from 'Helpers/apiErrors';
import { Store } from 'Store';
import Stats, { IStats } from 'Entities/Stats';
import StatsConfig, { IStatsConfig } from 'Entities/StatsConfig';
import TlsConfig, { ITlsConfig } from 'Entities/TlsConfig';
import { IClientsFindEntry } from 'Entities/ClientsFindEntry';
import ClientFindSubEntry from 'Entities/ClientFindSubEntry';
import FilterStatus, { IFilterStatus } from 'Entities/FilterStatus';
import { IStore } from './utils';
export default class Dashboard implements IStore {
rootStore: Store;
inited = false;
stats: Stats | undefined;
statsConfig: StatsConfig | undefined;
clientsInfo: Map<string, ClientFindSubEntry>;
tlsConfig: TlsConfig | undefined;
filteringConfig: FilterStatus | undefined;
constructor(rootStore: Store) {
this.rootStore = rootStore;
makeAutoObservable(this, {
rootStore: false,
inited: observable,
init: flow,
getStatsConfig: flow,
getTlsConfig: flow,
getClient: flow,
filteringStatus: flow,
stats: observable.ref,
statsConfig: observable.ref,
clientsInfo: observable.ref,
tlsConfig: observable.ref,
filteringConfig: observable.ref,
});
this.clientsInfo = new Map();
if (this.rootStore.login.loggedIn) {
this.init();
}
}
* init() {
yield this.getStatsConfig();
yield this.getTlsConfig();
yield this.getStats();
yield this.filteringStatus();
this.inited = true;
}
* getStats() {
const response = yield statsApi.stats();
const { result } = errorChecker<IStats>(response);
if (result) {
this.stats = new Stats(result);
if (this.stats.topClients) {
// TODO: fix bycicle
const topClients = this.stats.topClients.map((e) => {
return Object.keys(e.numberData)[0];
});
let firstClient = topClients.shift();
firstClient += '&';
const topClientsReq = firstClient + topClients.map((ip, index) => `ip${index + 1}=${ip}`).join('&');
yield this.getClient(topClientsReq);
}
}
}
* getClient(ip: string) {
// if & is encoding set in clientsFind qs options - encode: false
const response = yield clientsApi.clientsFind(ip);
const { result } = errorChecker<IClientsFindEntry[]>(response);
if (result) {
this.clientsInfo = new Map();
result.forEach((client) => {
const [clientIp, data] = Object.entries(client)[0];
this.clientsInfo.set(clientIp, new ClientFindSubEntry(data));
});
}
}
* getStatsConfig() {
const response = yield statsApi.statsInfo();
const { result } = errorChecker<IStatsConfig>(response);
if (result) {
this.statsConfig = new StatsConfig(result);
}
}
* getTlsConfig() {
const response = yield tlsApi.tlsStatus();
const { result } = errorChecker<ITlsConfig>(response);
if (result) {
this.tlsConfig = new TlsConfig(result);
}
}
* filteringStatus() {
const response = yield filteringApi.filteringStatus();
const { result } = errorChecker<IFilterStatus>(response);
if (result) {
this.filteringConfig = new FilterStatus(result);
}
}
}

View File

@@ -0,0 +1,218 @@
import { flow, makeAutoObservable, observable } from 'mobx';
import { Store } from 'Store';
import statsApi from 'Apis/stats';
import queryApi from 'Apis/log';
import safeBrowsingApi from 'Apis/safebrowsing';
import filteringApi from 'Apis/filtering';
import parentalApi from 'Apis/parental';
import safesearchApi from 'Apis/safesearch';
import StatsConfig, { IStatsConfig } from 'Entities/StatsConfig';
import QueryLogConfig, { IQueryLogConfig } from 'Entities/QueryLogConfig';
import FilterConfig, { IFilterConfig } from 'Entities/FilterConfig';
import FilterStatus, { IFilterStatus } from 'Entities/FilterStatus';
import { errorChecker } from 'Helpers/apiErrors';
import { IStore } from './utils';
export default class SomeStore implements IStore {
rootStore: Store;
inited = false;
statsConfig: StatsConfig | undefined;
queryLogConfig: QueryLogConfig | undefined;
safebrowsing: boolean | undefined;
filteringConfig: FilterConfig | undefined;
parental: boolean | undefined;
safesearch: boolean | undefined;
constructor(rootStore: Store) {
this.rootStore = rootStore;
makeAutoObservable(this, {
rootStore: false,
inited: observable,
init: flow,
statsConfig: observable.ref,
queryLogConfig: observable.ref,
safebrowsing: observable,
filteringConfig: observable.ref,
parental: observable,
safesearch: observable,
updateStatsConfig: flow,
statsInfo: flow,
statsReset: flow,
updateQueryLogConfig: flow,
queryLogInfo: flow,
querylogClear: flow,
safebrowsingDisable: flow,
safebrowsingEnable: flow,
safebrowsingStatus: flow,
updateFilteringConfig: flow,
filteringStatus: flow,
parentalDisable: flow,
parentalEnable: flow,
parentalStatus: flow,
safesearchDisable: flow,
safesearchEnable: flow,
safesearchStatus: flow,
});
}
* init() {
yield this.statsInfo();
yield this.queryLogInfo();
yield this.safebrowsingStatus();
yield this.filteringStatus();
yield this.parentalStatus();
yield this.safesearchStatus();
this.inited = yield true;
}
* updateStatsConfig(statsconfig: IStatsConfig) {
const response = yield statsApi.statsConfig(statsconfig);
const { result } = errorChecker<number>(response);
if (result) {
yield this.statsInfo();
}
}
* statsInfo() {
const response = yield statsApi.statsInfo();
const { result } = errorChecker<IStatsConfig>(response);
if (result) {
this.statsConfig = new StatsConfig(result);
}
}
* statsReset() {
const response = yield statsApi.statsReset();
const { result } = errorChecker(response);
if (result) {
yield this.statsInfo();
return true;
}
}
* updateQueryLogConfig(querylogconfig: IQueryLogConfig) {
const response = yield queryApi.queryLogConfig(querylogconfig);
const { result } = errorChecker<number>(response);
if (result) {
yield this.queryLogInfo();
}
}
* queryLogInfo() {
const response = yield queryApi.queryLogInfo();
const { result } = errorChecker<IQueryLogConfig>(response);
if (result) {
this.queryLogConfig = new QueryLogConfig(result);
}
}
* querylogClear() {
const response = yield queryApi.querylogClear();
const { result } = errorChecker(response);
if (result) {
yield this.queryLogInfo();
}
}
* safebrowsingDisable() {
const response = yield safeBrowsingApi.safebrowsingDisable();
const { result } = errorChecker<number>(response);
if (result) {
this.safebrowsing = false;
}
}
* safebrowsingEnable() {
const response = yield safeBrowsingApi.safebrowsingEnable();
const { result } = errorChecker(response);
if (result) {
this.safebrowsing = true;
}
}
* safebrowsingStatus() {
const response = yield safeBrowsingApi.safebrowsingStatus();
const { result } = errorChecker(response);
if (result) {
this.safebrowsing = result.enabled;
}
}
* updateFilteringConfig(filterconfig: IFilterConfig) {
const response = yield filteringApi.filteringConfig(filterconfig);
const { result } = errorChecker<number>(response);
if (result) {
yield this.filteringStatus();
}
}
* filteringStatus() {
const response = yield filteringApi.filteringStatus();
const { result } = errorChecker<IFilterStatus>(response);
if (result) {
this.filteringConfig = new FilterStatus(result);
}
}
* parentalDisable() {
const response = yield parentalApi.parentalDisable();
const { result } = errorChecker(response);
if (result) {
this.parental = false;
}
}
* parentalEnable() {
// TODO: remove magic;
const response = yield parentalApi.parentalEnable('sensitivity=TEEN');
const { result } = errorChecker(response);
if (result) {
this.parental = true;
}
}
* parentalStatus() {
const response = yield parentalApi.parentalStatus();
const { result } = errorChecker(response);
if (result) {
this.parental = result.enabled;
}
}
* safesearchDisable() {
const response = yield safesearchApi.safesearchDisable();
const { result } = errorChecker(response);
if (result) {
this.safesearch = false;
}
}
* safesearchEnable() {
const response = yield safesearchApi.safesearchEnable();
const { result } = errorChecker(response);
if (result) {
this.safesearch = true;
}
}
* safesearchStatus() {
const response = yield safesearchApi.safesearchStatus();
const { result } = errorChecker(response);
if (result) {
this.safesearch = result.enabled;
}
}
}

View File

@@ -6,7 +6,7 @@ import { IInitialConfigurationBeta } from 'Entities/InitialConfigurationBeta';
import { errorChecker } from 'Helpers/apiErrors';
import { flow, makeAutoObservable } from 'mobx';
import { Store } from 'Store';
import { Store } from 'Store/installStore';
export default class Install {
rootStore: Store;

View File

@@ -0,0 +1,45 @@
import { flow, makeAutoObservable, observable } from 'mobx';
import globalApi from 'Apis/global';
import { Store } from 'Store';
import { errorChecker } from 'Helpers/apiErrors';
import ProfileInfo, { IProfileInfo } from 'Entities/ProfileInfo';
import { ILogin } from 'Entities/Login';
export default class Login {
rootStore: Store;
loggedIn = false;
constructor(rootStore: Store) {
this.rootStore = rootStore;
makeAutoObservable(this, {
loggedIn: observable,
rootStore: false,
checkLoggedIn: flow,
login: flow,
});
this.checkLoggedIn();
}
* checkLoggedIn() {
const response = yield globalApi.getProfile();
const { result } = errorChecker<IProfileInfo>(response);
if (result) {
this.loggedIn = true;
this.rootStore.system.setProfile(new ProfileInfo(result));
this.rootStore.init();
}
// TODO: make smth with result, to not duplicate the request;
}
* login(login: ILogin) {
const response = yield globalApi.login(login);
const { result, error } = errorChecker(response);
if (result === 200) {
this.loggedIn = true;
return;
}
return error;
}
}

View File

@@ -0,0 +1,75 @@
import { flow, makeAutoObservable, observable, action } from 'mobx';
import globalApi from 'Apis/global';
import { Store } from 'Store';
import { errorChecker } from 'Helpers/apiErrors';
import ProfileInfo, { IProfileInfo } from 'Entities/ProfileInfo';
import ServerStatus, { IServerStatus } from 'Entities/ServerStatus';
import { IStore } from './utils';
export default class System implements IStore {
rootStore: Store;
inited = false;
status: ServerStatus | undefined;
profile: ProfileInfo | undefined;
constructor(rootStore: Store) {
this.rootStore = rootStore;
makeAutoObservable(this, {
rootStore: false,
inited: observable,
getServerStatus: flow,
init: flow,
setProfile: action,
switchServerStatus: flow,
getProfile: flow,
status: observable,
profile: observable,
});
if (this.rootStore.login.loggedIn) {
this.init();
}
}
* init() {
yield this.getServerStatus();
if (!this.profile) {
yield this.getProfile();
}
this.inited = true;
}
setProfile(profile: ProfileInfo) {
this.profile = profile;
}
* getProfile() {
const response = yield globalApi.getProfile();
const { result } = errorChecker<IProfileInfo>(response);
if (result) {
this.profile = new ProfileInfo(result);
}
}
* getServerStatus() {
const response = yield globalApi.status();
const { result } = errorChecker<IServerStatus>(response);
if (result) {
this.status = new ServerStatus(result);
}
}
* switchServerStatus(enable: boolean) {
const response = yield globalApi.dnsConfig({
protection_enabled: enable,
});
const { result } = errorChecker(response);
if (result) {
yield this.getServerStatus();
}
}
}

View File

@@ -1,25 +1,36 @@
import { makeAutoObservable, observable } from 'mobx';
import React from 'react';
import { makeAutoObservable, observable, action } from 'mobx';
import { translate } from '@adguard/translate';
import Translator, { DEFAULT_LOCALE, messages, Locale, reactFormater } from 'Localization';
import { Locale, DEFAULT_LOCALE, i18n } from 'Localization';
import { Store } from 'Store';
import { Store as InstallStore } from 'Store/installStore';
export default class UI {
rootStore: Store;
rootStore: Store | InstallStore;
currentLang = DEFAULT_LOCALE;
intl = new Translator<Locale>(Locale.en, messages, DEFAULT_LOCALE, reactFormater);
intl = translate.createReactTranslator<any>(i18n(this.currentLang), React);
constructor(rootStore: Store) {
sidebarOpen = false;
constructor(rootStore: Store | InstallStore) {
this.rootStore = rootStore;
makeAutoObservable(this, {
intl: observable.struct,
rootStore: false,
sidebarOpen: observable,
toggleSidebar: action,
});
}
updateLang = (lang: Locale) => {
this.currentLang = lang;
this.intl = this.intl.updateTranslator(lang);
this.intl = translate.createReactTranslator<any>(i18n(this.currentLang), React);
};
toggleSidebar = () => {
this.sidebarOpen = !this.sidebarOpen;
};
}

View File

@@ -0,0 +1,38 @@
import { Store } from 'Store';
export interface IStore {
rootStore: Store;
init: () => void;
inited: boolean;
}
/*
Each store should implement IStore to work properly if user not loggged in
and after log in like:
import { flow, makeAutoObservable, observable } from 'mobx';
import { Store } from 'Store';
import { IStore } from './utils';
export default class SomeStore implements IStore {
rootStore: Store;
inited = false;
constructor(rootStore: Store) {
this.rootStore = rootStore;
makeAutoObservable(this, {
rootStore: false,
inited: observable,
init: flow,
});
if (this.rootStore.login.loggedIn) {
this.init();
}
}
* init() {
this.inited = true;
}
}
*/