From 89a11b2567f12f85e608312f3b4d233d716e75dd Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 29 May 2024 19:52:24 +0300 Subject: [PATCH 01/23] feat: info-service tab --- src/components/nodes/NodesTable.vue | 23 +++- .../nodes/components/NodesTableHead.vue | 7 +- .../nodes/services/ServiceNodesTable.vue | 53 +++++++++ .../nodes/services/ServiceNodesTableItem.vue | 92 +++++++++++++++ src/lib/nodes/abstract.node.ts | 7 +- .../nodes/rate-info-service/RateInfoClient.ts | 33 ++++++ .../rate-info-service/RateInfoService.ts | 52 +++++++++ src/lib/nodes/rate-info-service/index.ts | 10 ++ src/lib/nodes/services.ts | 4 +- src/lib/nodes/types.ts | 5 + src/locales/de.json | 9 +- src/locales/en.json | 10 +- src/locales/ru.json | 10 +- src/locales/zh.json | 10 +- src/store/index.js | 6 +- src/store/modules/rate/index.js | 109 ------------------ src/store/modules/rate/index.ts | 17 +++ src/store/modules/rate/rate-actions.ts | 68 +++++++++++ src/store/modules/rate/rate-getters.ts | 26 +++++ src/store/modules/rate/rate-mutations.ts | 14 +++ src/store/modules/rate/rate-state.ts | 7 ++ src/store/modules/rate/types.ts | 6 + src/store/modules/services/index.ts | 17 +++ .../modules/services/services-actions.ts | 20 ++++ .../modules/services/services-getters.ts | 12 ++ .../modules/services/services-mutations.ts | 31 +++++ src/store/modules/services/services-plugin.ts | 33 ++++++ src/store/modules/services/services-state.ts | 6 + src/store/modules/services/types.ts | 6 + 29 files changed, 570 insertions(+), 133 deletions(-) create mode 100644 src/components/nodes/services/ServiceNodesTable.vue create mode 100644 src/components/nodes/services/ServiceNodesTableItem.vue create mode 100644 src/lib/nodes/rate-info-service/RateInfoClient.ts create mode 100644 src/lib/nodes/rate-info-service/RateInfoService.ts create mode 100644 src/lib/nodes/rate-info-service/index.ts delete mode 100644 src/store/modules/rate/index.js create mode 100644 src/store/modules/rate/index.ts create mode 100644 src/store/modules/rate/rate-actions.ts create mode 100644 src/store/modules/rate/rate-getters.ts create mode 100644 src/store/modules/rate/rate-mutations.ts create mode 100644 src/store/modules/rate/rate-state.ts create mode 100644 src/store/modules/rate/types.ts create mode 100644 src/store/modules/services/index.ts create mode 100644 src/store/modules/services/services-actions.ts create mode 100644 src/store/modules/services/services-getters.ts create mode 100644 src/store/modules/services/services-mutations.ts create mode 100644 src/store/modules/services/services-plugin.ts create mode 100644 src/store/modules/services/services-state.ts create mode 100644 src/store/modules/services/types.ts diff --git a/src/components/nodes/NodesTable.vue b/src/components/nodes/NodesTable.vue index 16e1ff6c1..7b531997b 100644 --- a/src/components/nodes/NodesTable.vue +++ b/src/components/nodes/NodesTable.vue @@ -2,6 +2,7 @@
{{ $t('nodes.tabs.adm_nodes') }} + {{ $t('nodes.tabs.service_nodes') }} {{ $t('nodes.tabs.coin_nodes') }} @@ -9,7 +10,9 @@ - + + + @@ -29,6 +32,20 @@
 
 
+
+ +
+ {{ $t('nodes.fastest_tooltip') }} +
+
 
 
+
- {{ t('nodes.coin') }} + + {{ customLabelCode ? t(customLabelCode) : t('nodes.coin') }} + {{ t('nodes.host') }} @@ -35,6 +37,9 @@ export default { }, hideSocket: { type: Boolean + }, + customLabelCode: { + type: String } }, setup() { diff --git a/src/components/nodes/services/ServiceNodesTable.vue b/src/components/nodes/services/ServiceNodesTable.vue new file mode 100644 index 000000000..a66149b87 --- /dev/null +++ b/src/components/nodes/services/ServiceNodesTable.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/nodes/services/ServiceNodesTableItem.vue b/src/components/nodes/services/ServiceNodesTableItem.vue new file mode 100644 index 000000000..6c54133d5 --- /dev/null +++ b/src/components/nodes/services/ServiceNodesTableItem.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/lib/nodes/abstract.node.ts b/src/lib/nodes/abstract.node.ts index 193049507..0b003b8d0 100644 --- a/src/lib/nodes/abstract.node.ts +++ b/src/lib/nodes/abstract.node.ts @@ -1,13 +1,8 @@ import { getHealthCheckInterval } from './utils/getHealthcheckConfig' import { TNodeLabel } from './constants' -import { HealthcheckInterval, NodeKind, NodeStatus, NodeType } from './types' +import { HealthcheckInterval, HealthcheckResult, NodeKind, NodeStatus, NodeType } from './types' import { nodesStorage } from './storage' -type HealthcheckResult = { - height: number - ping: number -} - type HttpProtocol = 'http:' | 'https:' type WsProtocol = 'ws:' | 'wss:' diff --git a/src/lib/nodes/rate-info-service/RateInfoClient.ts b/src/lib/nodes/rate-info-service/RateInfoClient.ts new file mode 100644 index 000000000..eaa89ba31 --- /dev/null +++ b/src/lib/nodes/rate-info-service/RateInfoClient.ts @@ -0,0 +1,33 @@ +import { Client } from '@/lib/nodes/abstract.client.ts' +import { RateInfoResponse, RateInfoService } from '@/lib/nodes/rate-info-service/RateInfoService.ts' + +export class RateInfoClient extends Client { + constructor(endpoints: string[] = [], minNodeVersion = '0.0.0') { + super('adm') + this.nodes = endpoints.map((endpoint) => new RateInfoService(endpoint)) + this.minNodeVersion = minNodeVersion + + void this.watchNodeStatusChange() + } + + async getAllRates(): Promise { + const node = await this.fetchNode() + return await node.getAllRates() + } + + async getHistory(timestamp: number) { + const node = await this.fetchNode() + return await node.getHistory(timestamp) + } + + async fetchNode() { + const node = this.useFastest ? this.getFastestNode() : this.getRandomNode() + if (!node) { + // All nodes seem to be offline: let's refresh the statuses + this.checkHealth() + // But there's nothing we can do right now + return Promise.reject(new Error('No online nodes at the moment')) + } + return node + } +} diff --git a/src/lib/nodes/rate-info-service/RateInfoService.ts b/src/lib/nodes/rate-info-service/RateInfoService.ts new file mode 100644 index 000000000..0cd90a8e8 --- /dev/null +++ b/src/lib/nodes/rate-info-service/RateInfoService.ts @@ -0,0 +1,52 @@ +import { Node } from '@/lib/nodes/abstract.node' +import axios, { AxiosInstance } from 'axios' +import { HealthcheckResult } from '@/lib/nodes/types.ts' +import { NODE_LABELS } from '@/lib/nodes/constants.ts' + +export type RateInfoResponse = { + success: boolean + date: number + result: Record +} + +export type RateHistoryInfoResponse = { + success: boolean + date: number + result: { + _id: string + date: number + tickers: Record + }[] +} + +export class RateInfoService extends Node { + constructor(url: string) { + super(url, 'adm', 'service', NODE_LABELS.RatesInfo) + } + protected buildClient(): AxiosInstance { + return axios.create({ + baseURL: this.url + }) + } + + async getAllRates(): Promise { + const response = await this.client.get('/get') + return response.data + } + + async getHistory(timestamp: number) { + const response = await this.client.get( + `/getHistory?timestamp=${timestamp}` + ) + return response.data + } + + protected async checkHealth(): Promise { + const start = Date.now() + const response = await this.getAllRates() + return { + ping: Date.now() - start, + height: response.date + } + } +} diff --git a/src/lib/nodes/rate-info-service/index.ts b/src/lib/nodes/rate-info-service/index.ts new file mode 100644 index 000000000..00e4522ba --- /dev/null +++ b/src/lib/nodes/rate-info-service/index.ts @@ -0,0 +1,10 @@ +import config from '@/config' +import { NodeInfo } from '@/types/wallets' +import { RateInfoClient } from '@/lib/nodes/rate-info-service/RateInfoClient.ts' + +const endpoints = (config.adm.services.list.infoService as NodeInfo[]).map( + (endpoint) => endpoint.url +) +export const rateInfoClient = new RateInfoClient(endpoints) + +export default rateInfoClient diff --git a/src/lib/nodes/services.ts b/src/lib/nodes/services.ts index ee2688adf..a271b285e 100644 --- a/src/lib/nodes/services.ts +++ b/src/lib/nodes/services.ts @@ -1,7 +1,9 @@ import { lskIndexer } from './lsk-indexer' import { ethIndexer } from './eth-indexer' +import { rateInfoClient } from './rate-info-service' export const services = { lskIndexer, - ethIndexer + ethIndexer, + rate: rateInfoClient } diff --git a/src/lib/nodes/types.ts b/src/lib/nodes/types.ts index d938906fb..9fbe4c2c7 100644 --- a/src/lib/nodes/types.ts +++ b/src/lib/nodes/types.ts @@ -9,3 +9,8 @@ export type NodeType = 'adm' | 'eth' | 'btc' | 'doge' | 'dash' | 'lsk' export type NodeKind = 'node' | 'service' export type HealthcheckInterval = 'normal' | 'crucial' | 'onScreen' + +export type HealthcheckResult = { + height: number + ping: number +} diff --git a/src/locales/de.json b/src/locales/de.json index 24871f12a..0c338b1fa 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -124,7 +124,14 @@ "ms": "ms", "nodeLabelDescription": "Support decentralization and enhance privacy level—run your own ADAMANT node and web application for messaging. Node can be run following instructions. To add your nodes in the list for connection, you need to deploy web application on separate domain. This limitation refers to Content Security Policy (CSP). In iOS and Android applications there are no such limitations.", "offline": "Offline", - "ping": "Ping" + "ping": "Ping", + "service": "Service", + "socket": "Socket", + "tabs": { + "adm_nodes": "ADM Knooppunten", + "coin_nodes": "Munt Knooppunten", + "service_nodes": "Service Knooppunten" + } }, "notifications": { "message": { diff --git a/src/locales/en.json b/src/locales/en.json index 8924e9271..0228d65d6 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -163,10 +163,6 @@ "active": "Active", "fastest_title": "Choose the fastest node", "fastest_tooltip": "Requests will be processed with a fastest node", - "tabs": { - "adm_nodes": "ADM nodes", - "coin_nodes": "Coin nodes" - }, "label": "Type", "host": "Host", "inactive": "Inactive", @@ -176,7 +172,13 @@ "offline": "Offline", "coin": "Coin", "ping": "Ping", + "service": "Service", "socket": "Socket", + "tabs": { + "adm_nodes": "ADM nodes", + "coin_nodes": "Coin nodes", + "service_nodes": "Service nodes" + }, "unsupported": "Unsupported", "unsupported_reason_protocol": "HTTP is not allowed", "unsupported_reason_api_version": "Outdated API version", diff --git a/src/locales/ru.json b/src/locales/ru.json index 45720cbd9..aff5448f4 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -164,10 +164,6 @@ "active": "Активная", "fastest_title": "Подключаться к самому быстрому узлу", "fastest_tooltip": "Запросы будут обрабатываться самым быстрым узлом", - "tabs": { - "adm_nodes": "ADM ноды", - "coin_nodes": "Coin ноды" - }, "label": "Тип", "host": "Хост", "inactive": "Неактивная", @@ -177,7 +173,13 @@ "offline": "Недоступна", "coin": "Coin", "ping": "Пинг", + "service": "Сервис", "socket": "Сокеты", + "tabs": { + "adm_nodes": "ADM ноды", + "coin_nodes": "Coin ноды", + "service_nodes": "Сервис ноды" + }, "unsupported": "Не поддерживается", "unsupported_reason_protocol": "HTTP не поддерживается", "unsupported_reason_api_version": "API версия устарела", diff --git a/src/locales/zh.json b/src/locales/zh.json index 486b0760e..762d4f50d 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -163,10 +163,6 @@ "active": "活动", "fastest_title": "选择最快的节点", "fastest_tooltip": "将使用最快的节点处理请求", - "tabs": { - "adm_nodes": "ADM节点", - "coin_nodes": "硬币节点" - }, "label": "类型", "host": "主机", "inactive": "Inactive", @@ -176,7 +172,13 @@ "offline": "脱机", "coin": "硬币", "ping": "嗨", + "service": "服务", "socket": "套接字", + "tabs": { + "adm_nodes": "ADM节点", + "coin_nodes": "硬币节点", + "service_nodes": "服务节点" + }, "unsupported": "不受欢迎", "unsupported_reason_protocol": "不允许HTTP", "unsupported_reason_api_version": "API版本过期", diff --git a/src/store/index.js b/src/store/index.js index f830565da..88d7e2338 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -42,6 +42,8 @@ import cache from '@/store/cache' import rate from './modules/rate' import { cryptoTransferAsset, replyWithCryptoTransferAsset } from '@/lib/adamant-api/asset' import { PendingTxStore } from '@/lib/pending-transactions' +import servicesModule from './modules/services' +import servicesPlugin from './modules/services/services-plugin' export let interval @@ -245,6 +247,7 @@ const store = { identicon, notification, rate, + servicesModule, wallets: walletsModule // Wallets order and visibility } } @@ -261,7 +264,8 @@ registerVuexPlugins(storeInstance, [ navigatorOnline, socketsPlugin, botCommandsPlugin, - walletsPersistencePlugin + walletsPersistencePlugin, + servicesPlugin ]) export { store } // for tests diff --git a/src/store/modules/rate/index.js b/src/store/modules/rate/index.js deleted file mode 100644 index 13c85b7ed..000000000 --- a/src/store/modules/rate/index.js +++ /dev/null @@ -1,109 +0,0 @@ -import axios from 'axios' -import { getRandomServiceUrl } from '@/config/utils' - -const state = () => ({ - rates: {}, - isLoaded: false, - historyRates: {} -}) - -export let interval - -const UPDATE_RATES_INTERVAL = 90000 -const mutations = { - setRates(state, rates) { - state.rates = rates - }, - setHistoryRates(state, historyRates) { - state.historyRates[historyRates.name] = historyRates.value - }, - loadRates(state) { - state.isLoaded = true - } -} -const actions = { - getAllRates({ commit }) { - const url = getRandomServiceUrl('adm', 'infoService') - return new Promise((resolve, reject) => { - axios - .get(`${url}/get`) - .then((res) => { - const rates = res.data.result - commit('setRates', rates) - commit('loadRates') - resolve(res) - }) - .catch((err) => { - reject(err) - }) - }) - }, - getHistoryRates({ state, commit }, { timestamp }) { - if (!timestamp) return - const url = getRandomServiceUrl('adm', 'infoService') - if (state.historyRates[timestamp] !== undefined) { - return state.historyRates[timestamp] - } else { - return new Promise((resolve, reject) => { - axios - .get(`${url}/getHistory?timestamp=${timestamp}`) - .then((res) => { - const rates = res?.data?.result[0]?.tickers ?? null - commit('setHistoryRates', { name: timestamp, value: rates }) - resolve(res) - }) - .catch((err) => { - reject(err) - }) - }) - } - }, - startInterval: { - root: true, - handler({ dispatch }) { - function repeat() { - dispatch('getAllRates') - .catch((err) => console.error(err)) - .then(() => { - interval = setTimeout(repeat, UPDATE_RATES_INTERVAL) - }) - } - - repeat() - } - }, - stopInterval: { - root: true, - handler() { - clearTimeout(interval) - } - } -} -const getters = { - historyRate: (state, getters, rootState) => (timestamp, amount, crypto) => { - let historyRate - const currentCurrency = rootState.options.currentRate - const store = state.historyRates[timestamp] - if (store) { - historyRate = `${(store[`${crypto}/${currentCurrency}`] * amount).toFixed( - 2 - )} ${currentCurrency}` - } else { - historyRate = '�' - } - return historyRate - }, - rate: (state, getters, rootState) => (amount, crypto) => { - const currentCurrency = rootState.options.currentRate - const store = state.rates[`${crypto}/${currentCurrency}`] - const rate = store * amount - return isNaN(rate) ? '�' : `${rate.toFixed(2)} ${currentCurrency}` - } -} -export default { - state, - getters, - mutations, - actions, - namespaced: true -} diff --git a/src/store/modules/rate/index.ts b/src/store/modules/rate/index.ts new file mode 100644 index 000000000..732064b9b --- /dev/null +++ b/src/store/modules/rate/index.ts @@ -0,0 +1,17 @@ +import { Module } from 'vuex' +import { RootState } from '@/store/types' +import { RateState } from '@/store/modules/rate/types.ts' +import { state } from '@/store/modules/rate/rate-state.ts' +import { mutations } from '@/store/modules/rate/rate-mutations.ts' +import { actions } from '@/store/modules/rate/rate-actions.ts' +import { getters } from '@/store/modules/rate/rate-getters.ts' + +const rateModule: Module = { + namespaced: true, + state, + mutations, + actions, + getters +} + +export default rateModule diff --git a/src/store/modules/rate/rate-actions.ts b/src/store/modules/rate/rate-actions.ts new file mode 100644 index 000000000..004aa1db7 --- /dev/null +++ b/src/store/modules/rate/rate-actions.ts @@ -0,0 +1,68 @@ +import { ActionTree } from 'vuex' +import { RootState } from '@/store/types' +import rateInfoClient from '@/lib/nodes/rate-info-service' +import { RateState } from '@/store/modules/rate/types.ts' + +export let interval: NodeJS.Timeout + +const UPDATE_RATES_INTERVAL = 90000 + +export const actions: ActionTree = { + getAllRates({ commit }) { + return new Promise((resolve, reject) => { + rateInfoClient + .getAllRates() + .then(({ result, success }) => { + if (success) { + commit('setRates', result) + } + commit('loadRates') + resolve(result) + }) + .catch((err) => { + reject(err) + }) + }) + }, + getHistoryRates({ state, commit }, { timestamp }) { + if (!timestamp) return + if (state.historyRates[timestamp] !== undefined) { + return state.historyRates[timestamp] + } else { + return new Promise((resolve, reject) => { + rateInfoClient + .getHistory(timestamp) + .then(({ result, success }) => { + if (success) { + const rates = result[0]?.tickers ?? null + commit('setHistoryRates', { name: timestamp, value: rates }) + } + resolve(result) + }) + .catch((err) => { + reject(err) + }) + }) + } + }, + startInterval: { + root: true, + handler({ dispatch }) { + function repeat() { + dispatch('getAllRates') + .catch((err) => console.error(err)) + .then(() => { + interval = setTimeout(repeat, UPDATE_RATES_INTERVAL) + }) + } + + repeat() + } + }, + stopInterval: { + root: true, + handler() { + clearTimeout(interval) + } + } +} diff --git a/src/store/modules/rate/rate-getters.ts b/src/store/modules/rate/rate-getters.ts new file mode 100644 index 000000000..aad52b153 --- /dev/null +++ b/src/store/modules/rate/rate-getters.ts @@ -0,0 +1,26 @@ +import { GetterTree } from 'vuex' +import { RootState } from '@/store/types' +import { RateState } from '@/store/modules/rate/types.ts' + +export const getters: GetterTree = { + historyRate: + (state, getters, rootState) => (timestamp: number, amount: number, crypto: string) => { + let historyRate + const currentCurrency = rootState.options.currentRate + const store = state.historyRates[timestamp] + if (store) { + historyRate = `${(store[`${crypto}/${currentCurrency}`] * amount).toFixed( + 2 + )} ${currentCurrency}` + } else { + historyRate = '�' + } + return historyRate + }, + rate: (state, getters, rootState) => (amount: number, crypto: string) => { + const currentCurrency = rootState.options.currentRate + const store = state.rates[`${crypto}/${currentCurrency}`] + const rate = store * amount + return isNaN(rate) ? '�' : `${rate.toFixed(2)} ${currentCurrency}` + } +} diff --git a/src/store/modules/rate/rate-mutations.ts b/src/store/modules/rate/rate-mutations.ts new file mode 100644 index 000000000..6a1add41f --- /dev/null +++ b/src/store/modules/rate/rate-mutations.ts @@ -0,0 +1,14 @@ +import { MutationTree } from 'vuex' +import { Rates, RateState } from '@/store/modules/rate/types.ts' + +export const mutations: MutationTree = { + setRates(state, rates: Rates) { + state.rates = rates + }, + setHistoryRates(state, historyRates: { name: number; value: Rates }) { + state.historyRates[historyRates.name] = historyRates.value + }, + loadRates(state) { + state.isLoaded = true + } +} diff --git a/src/store/modules/rate/rate-state.ts b/src/store/modules/rate/rate-state.ts new file mode 100644 index 000000000..be557dd70 --- /dev/null +++ b/src/store/modules/rate/rate-state.ts @@ -0,0 +1,7 @@ +import { RateState } from '@/store/modules/rate/types.ts' + +export const state: RateState = { + rates: {}, + isLoaded: false, + historyRates: {} +} diff --git a/src/store/modules/rate/types.ts b/src/store/modules/rate/types.ts new file mode 100644 index 000000000..4b86448eb --- /dev/null +++ b/src/store/modules/rate/types.ts @@ -0,0 +1,6 @@ +export type Rates = Record +export type RateState = { + rates: Rates + isLoaded: boolean + historyRates: Record +} diff --git a/src/store/modules/services/index.ts b/src/store/modules/services/index.ts new file mode 100644 index 000000000..10c8741e8 --- /dev/null +++ b/src/store/modules/services/index.ts @@ -0,0 +1,17 @@ +import { state } from './services-state.ts' +import { actions } from './services-actions.ts' +import { mutations } from './services-mutations.ts' +import { getters } from './services-getters.ts' +import { Module } from 'vuex' +import { RootState } from '@/store/types' +import { ServicesState } from '@/store/modules/services/types.ts' + +const servicesModule: Module = { + namespaced: true, + state, + actions, + mutations, + getters +} + +export default servicesModule diff --git a/src/store/modules/services/services-actions.ts b/src/store/modules/services/services-actions.ts new file mode 100644 index 000000000..9995cfe62 --- /dev/null +++ b/src/store/modules/services/services-actions.ts @@ -0,0 +1,20 @@ +import { services } from '@/lib/nodes/services' +import { ActionTree } from 'vuex' +import { RootState } from '@/store/types' +import { ServicesState } from '@/store/modules/services/types.ts' + +export const actions: ActionTree = { + updateStatus() { + for (const [, client] of Object.entries(services)) { + client.checkHealth() + } + }, + + toggle(context, payload) { + context.commit('toggle', payload) + }, + + useFastestService(context, payload) { + context.commit('useFastestService', payload) + } +} diff --git a/src/store/modules/services/services-getters.ts b/src/store/modules/services/services-getters.ts new file mode 100644 index 000000000..214287a09 --- /dev/null +++ b/src/store/modules/services/services-getters.ts @@ -0,0 +1,12 @@ +import { GetterTree } from 'vuex' +import { RootState } from '@/store/types' +import { ServicesState } from '@/store/modules/services/types.ts' + +export const getters: GetterTree = { + rate(state) { + return Object.values(state.rate) + }, + services(state, getters) { + return [...getters.rate] + } +} diff --git a/src/store/modules/services/services-mutations.ts b/src/store/modules/services/services-mutations.ts new file mode 100644 index 000000000..563ad6fe3 --- /dev/null +++ b/src/store/modules/services/services-mutations.ts @@ -0,0 +1,31 @@ +import { MutationTree } from 'vuex' +import { AvailableService, ServicesState } from '@/store/modules/services/types.ts' + +export const mutations: MutationTree = { + useFastestService(state, value) { + state.useFastestService = value + }, + + toggle(state, payload: { type: AvailableService; url: string; active: boolean }) { + if (!state[payload.type]) { + state[payload.type] = {} + } + const node = state[payload.type][payload.url] + if (node) { + node.active = payload.active + } + }, + + status( + state, + { + status, + serviceType + }: { serviceType: AvailableService; status: { url: string; active: boolean } } + ) { + if (!state[serviceType]) { + state[serviceType] = {} + } + state[serviceType][status.url] = status + } +} diff --git a/src/store/modules/services/services-plugin.ts b/src/store/modules/services/services-plugin.ts new file mode 100644 index 000000000..22fa5e7b0 --- /dev/null +++ b/src/store/modules/services/services-plugin.ts @@ -0,0 +1,33 @@ +import { services } from '@/lib/nodes/services' +import { Store } from 'vuex' +import { AvailableService, ServicesState } from '@/store/modules/services/types.ts' + +export default (store: Store) => { + for (const [serviceType, client] of Object.entries(services)) { + client + .getNodes() + .forEach((status) => store.commit('servicesModule/status', { status, serviceType })) + + client.onStatusUpdate((status) => { + store.commit('servicesModule/status', { status, serviceType }) + }) + } + store.commit('servicesModule/useFastestService', services.rate.useFastest) + + store.subscribe((mutation) => { + const { type, payload } = mutation + + if (type === 'servicesModule/useFastestService') { + services.rate.setUseFastest(!!payload) + } + + if (type === 'servicesModule/toggle') { + const selectedNodeType = payload.type as AvailableService + const newStatus = services[selectedNodeType].toggleNode(payload.url, payload.active) + + if (newStatus) { + store.commit('servicesModule/status', { status: newStatus, serviceType: selectedNodeType }) + } + } + }) +} diff --git a/src/store/modules/services/services-state.ts b/src/store/modules/services/services-state.ts new file mode 100644 index 000000000..9a78028f5 --- /dev/null +++ b/src/store/modules/services/services-state.ts @@ -0,0 +1,6 @@ +import { ServicesState } from '@/store/modules/services/types.ts' + +export const state: ServicesState = { + rate: {}, + useFastestService: true +} diff --git a/src/store/modules/services/types.ts b/src/store/modules/services/types.ts new file mode 100644 index 000000000..817ef8909 --- /dev/null +++ b/src/store/modules/services/types.ts @@ -0,0 +1,6 @@ +export type ServicesState = { + rate: Record + useFastestService: true +} + +export type AvailableService = 'rates-info' From 833e831f531831958d09b95a9f2563341ee083d5 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 29 May 2024 21:27:08 +0300 Subject: [PATCH 02/23] fix: toggle active --- src/lib/nodes/constants.ts | 4 ++-- src/lib/nodes/rate-info-service/RateInfoService.ts | 2 +- src/store/modules/services/types.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/nodes/constants.ts b/src/lib/nodes/constants.ts index 6cd0ddee3..78824cc72 100644 --- a/src/lib/nodes/constants.ts +++ b/src/lib/nodes/constants.ts @@ -9,7 +9,7 @@ export type TNodeLabel = | 'dash-node' | 'lsk-node' | 'lsk-indexer' - | 'rates-info' + | 'rate' type KebabToCamelCase = S extends `${infer T}-${infer U}` ? `${T}${Capitalize>}` @@ -38,5 +38,5 @@ export const NODE_LABELS: NodeLabels = { DashNode: 'dash-node', LskNode: 'lsk-node', LskIndexer: 'lsk-indexer', - RatesInfo: 'rates-info' + Rate: 'rate' } diff --git a/src/lib/nodes/rate-info-service/RateInfoService.ts b/src/lib/nodes/rate-info-service/RateInfoService.ts index 0cd90a8e8..e1931296f 100644 --- a/src/lib/nodes/rate-info-service/RateInfoService.ts +++ b/src/lib/nodes/rate-info-service/RateInfoService.ts @@ -21,7 +21,7 @@ export type RateHistoryInfoResponse = { export class RateInfoService extends Node { constructor(url: string) { - super(url, 'adm', 'service', NODE_LABELS.RatesInfo) + super(url, 'adm', 'service', NODE_LABELS.Rate) } protected buildClient(): AxiosInstance { return axios.create({ diff --git a/src/store/modules/services/types.ts b/src/store/modules/services/types.ts index 817ef8909..ead3e9a79 100644 --- a/src/store/modules/services/types.ts +++ b/src/store/modules/services/types.ts @@ -3,4 +3,4 @@ export type ServicesState = { useFastestService: true } -export type AvailableService = 'rates-info' +export type AvailableService = 'rate' From d532716660401b4f65aa3dfbdedd4dcf48d36db7 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 12 Jul 2024 17:55:03 +0300 Subject: [PATCH 03/23] fix(RateInfoService.ts): incorrect label --- src/lib/nodes/rate-info-service/RateInfoService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/nodes/rate-info-service/RateInfoService.ts b/src/lib/nodes/rate-info-service/RateInfoService.ts index e1931296f..0cd90a8e8 100644 --- a/src/lib/nodes/rate-info-service/RateInfoService.ts +++ b/src/lib/nodes/rate-info-service/RateInfoService.ts @@ -21,7 +21,7 @@ export type RateHistoryInfoResponse = { export class RateInfoService extends Node { constructor(url: string) { - super(url, 'adm', 'service', NODE_LABELS.Rate) + super(url, 'adm', 'service', NODE_LABELS.RatesInfo) } protected buildClient(): AxiosInstance { return axios.create({ From c6eaf6e9720e233a40aa46896a0433f176411de9 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 12 Jul 2024 23:48:22 +0300 Subject: [PATCH 04/23] fix: useFastest checkbox, refactoring (node kind) --- src/components/nodes/NodesTable.vue | 16 +++++++-- .../nodes/services/ServiceNodesTable.vue | 2 +- .../nodes/services/ServiceNodesTableItem.vue | 4 +-- src/lib/nodes/abstract.client.ts | 11 ++++-- .../nodes/rate-info-service/RateInfoClient.ts | 2 +- src/lib/nodes/services.ts | 2 +- src/lib/nodes/storage.ts | 35 ++++++++++++------- src/store/index.js | 2 +- .../modules/services/services-getters.ts | 2 +- .../modules/services/services-mutations.ts | 13 +++---- src/store/modules/services/services-plugin.ts | 16 ++++----- src/store/modules/services/services-state.ts | 2 +- src/store/modules/services/types.ts | 4 +-- 13 files changed, 65 insertions(+), 46 deletions(-) diff --git a/src/components/nodes/NodesTable.vue b/src/components/nodes/NodesTable.vue index 7b531997b..674180fc4 100644 --- a/src/components/nodes/NodesTable.vue +++ b/src/components/nodes/NodesTable.vue @@ -34,7 +34,7 @@
({ + get() { + return store.state.services.useFastestService + }, + set(value) { + store.dispatch('services/useFastestService', value) + } + }) + return { tab, classes, useSocketConnection, preferFastestAdmNodeOption, - preferFastestCoinNodeOption + preferFastestCoinNodeOption, + preferFasterServiceNodeOption } } }) diff --git a/src/components/nodes/services/ServiceNodesTable.vue b/src/components/nodes/services/ServiceNodesTable.vue index a66149b87..a4c1c40c5 100644 --- a/src/components/nodes/services/ServiceNodesTable.vue +++ b/src/components/nodes/services/ServiceNodesTable.vue @@ -37,7 +37,7 @@ export default defineComponent({ const store = useStore() const nodes = computed(() => { - const arr = store.getters['servicesModule/services'] + const arr = store.getters['services/services'] return [...arr].sort(sortNodesFn) }) diff --git a/src/components/nodes/services/ServiceNodesTableItem.vue b/src/components/nodes/services/ServiceNodesTableItem.vue index 6c54133d5..77c99ffa9 100644 --- a/src/components/nodes/services/ServiceNodesTableItem.vue +++ b/src/components/nodes/services/ServiceNodesTableItem.vue @@ -64,12 +64,12 @@ export default { const type = computed(() => props.node.label) const toggleActiveStatus = () => { - store.dispatch('servicesModule/toggle', { + store.dispatch('services/toggle', { type: type.value, url: url.value, active: !active.value }) - store.dispatch('servicesModule/updateStatus') + store.dispatch('services/updateStatus') } return { diff --git a/src/lib/nodes/abstract.client.ts b/src/lib/nodes/abstract.client.ts index 71fa48ad0..0f7e61fc3 100644 --- a/src/lib/nodes/abstract.client.ts +++ b/src/lib/nodes/abstract.client.ts @@ -1,4 +1,4 @@ -import type { HealthcheckInterval, NodeType } from '@/lib/nodes/types' +import type { HealthcheckInterval, NodeKind, NodeType } from '@/lib/nodes/types' import { AllNodesOfflineError } from './utils/errors' import { filterSyncedNodes } from './utils/filterSyncedNodes' import { Node } from './abstract.node' @@ -21,13 +21,18 @@ export abstract class Client { * A callback that is called every time a node status is updated */ statusUpdateCallback?: (status: ReturnType) => void + /** + * Node kind + */ + kind: NodeKind /** * Node type */ type: NodeType - constructor(type: NodeType) { + constructor(type: NodeType, kind: NodeKind = 'node') { this.type = type + this.kind = kind this.useFastest = nodesStorage.getUseFastest(type) } @@ -100,7 +105,7 @@ export abstract class Client { setUseFastest(state: boolean) { this.useFastest = state - nodesStorage.setUseFastest(state, this.type) + nodesStorage.setUseFastest(state, this.type, this.kind) } /** diff --git a/src/lib/nodes/rate-info-service/RateInfoClient.ts b/src/lib/nodes/rate-info-service/RateInfoClient.ts index eaa89ba31..24914b273 100644 --- a/src/lib/nodes/rate-info-service/RateInfoClient.ts +++ b/src/lib/nodes/rate-info-service/RateInfoClient.ts @@ -3,7 +3,7 @@ import { RateInfoResponse, RateInfoService } from '@/lib/nodes/rate-info-service export class RateInfoClient extends Client { constructor(endpoints: string[] = [], minNodeVersion = '0.0.0') { - super('adm') + super('adm', 'service') this.nodes = endpoints.map((endpoint) => new RateInfoService(endpoint)) this.minNodeVersion = minNodeVersion diff --git a/src/lib/nodes/services.ts b/src/lib/nodes/services.ts index bd7412760..e259775d5 100644 --- a/src/lib/nodes/services.ts +++ b/src/lib/nodes/services.ts @@ -5,5 +5,5 @@ import { rateInfoClient } from './rate-info-service' export const services = { klyIndexer, ethIndexer, - rate: rateInfoClient + 'rates-info': rateInfoClient } diff --git a/src/lib/nodes/storage.ts b/src/lib/nodes/storage.ts index 0662633b5..9b7da1659 100644 --- a/src/lib/nodes/storage.ts +++ b/src/lib/nodes/storage.ts @@ -1,5 +1,5 @@ import { TypedStorage } from '@/lib/typed-storage' -import type { NodeType } from './types' +import { NodeKind, NodeType } from './types' type URL = string type Enabled = boolean @@ -8,7 +8,12 @@ type State = Record type Options = Record< NodeType, { - useFastest: boolean + node: { + useFastest: boolean + } + service?: { + useFastest: boolean + } } > @@ -18,12 +23,15 @@ const NODES_OPTIONS_STORAGE_KEY = 'NODES_OPTIONS_STORAGE' // for storing common const stateStorage = new TypedStorage(NODES_STATE_STORAGE_KEY, {} as State, window.localStorage) const defaultOptions: Options = { - adm: { useFastest: false }, - btc: { useFastest: true }, - doge: { useFastest: true }, - dash: { useFastest: true }, - eth: { useFastest: true }, - kly: { useFastest: true } + adm: { + node: { useFastest: false }, + service: { useFastest: true } + }, + btc: { node: { useFastest: false } }, + doge: { node: { useFastest: false } }, + dash: { node: { useFastest: false } }, + eth: { node: { useFastest: false } }, + kly: { node: { useFastest: false }, service: { useFastest: true } } } const optionsStorage = new TypedStorage( @@ -44,18 +52,19 @@ export const nodesStorage = { stateStorage.setItem(state) }, - getUseFastest(node: NodeType) { + getUseFastest(node: NodeType, kind: NodeKind = 'node') { const options = optionsStorage.getItem() - return !!options[node]?.useFastest + return !!options[node][kind]?.useFastest }, - setUseFastest(value: boolean, node: NodeType) { + setUseFastest(value: boolean, node: NodeType, kind: NodeKind = 'node') { const options = optionsStorage.getItem() if (!options[node]) { - options[node] = { useFastest: value } + options[node] = { node: { useFastest: false } } + options[node][kind] = { useFastest: value } } - options[node].useFastest = value + options[node][kind] = { useFastest: value } optionsStorage.setItem(options) } diff --git a/src/store/index.js b/src/store/index.js index 891bbc175..566a74681 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -252,7 +252,7 @@ const store = { identicon, notification, rate, - servicesModule, + services: servicesModule, wallets: walletsModule // Wallets order and visibility } } diff --git a/src/store/modules/services/services-getters.ts b/src/store/modules/services/services-getters.ts index 214287a09..647cacc19 100644 --- a/src/store/modules/services/services-getters.ts +++ b/src/store/modules/services/services-getters.ts @@ -4,7 +4,7 @@ import { ServicesState } from '@/store/modules/services/types.ts' export const getters: GetterTree = { rate(state) { - return Object.values(state.rate) + return Object.values(state['rates-info']) }, services(state, getters) { return [...getters.rate] diff --git a/src/store/modules/services/services-mutations.ts b/src/store/modules/services/services-mutations.ts index 563ad6fe3..d0d3f2757 100644 --- a/src/store/modules/services/services-mutations.ts +++ b/src/store/modules/services/services-mutations.ts @@ -1,12 +1,15 @@ import { MutationTree } from 'vuex' import { AvailableService, ServicesState } from '@/store/modules/services/types.ts' +type StatusPayload = { serviceType: AvailableService; status: { url: string; active: boolean } } +type TogglePayload = { type: AvailableService; url: string; active: boolean } + export const mutations: MutationTree = { useFastestService(state, value) { state.useFastestService = value }, - toggle(state, payload: { type: AvailableService; url: string; active: boolean }) { + toggle(state, payload: TogglePayload) { if (!state[payload.type]) { state[payload.type] = {} } @@ -16,13 +19,7 @@ export const mutations: MutationTree = { } }, - status( - state, - { - status, - serviceType - }: { serviceType: AvailableService; status: { url: string; active: boolean } } - ) { + status(state, { status, serviceType }: StatusPayload) { if (!state[serviceType]) { state[serviceType] = {} } diff --git a/src/store/modules/services/services-plugin.ts b/src/store/modules/services/services-plugin.ts index 22fa5e7b0..04d8b172a 100644 --- a/src/store/modules/services/services-plugin.ts +++ b/src/store/modules/services/services-plugin.ts @@ -4,29 +4,27 @@ import { AvailableService, ServicesState } from '@/store/modules/services/types. export default (store: Store) => { for (const [serviceType, client] of Object.entries(services)) { - client - .getNodes() - .forEach((status) => store.commit('servicesModule/status', { status, serviceType })) + client.getNodes().forEach((status) => store.commit('services/status', { status, serviceType })) client.onStatusUpdate((status) => { - store.commit('servicesModule/status', { status, serviceType }) + store.commit('services/status', { status, serviceType }) }) } - store.commit('servicesModule/useFastestService', services.rate.useFastest) + store.commit('services/useFastestService', services['rates-info'].useFastest) store.subscribe((mutation) => { const { type, payload } = mutation - if (type === 'servicesModule/useFastestService') { - services.rate.setUseFastest(!!payload) + if (type === 'services/useFastestService') { + services['rates-info'].setUseFastest(!!payload) } - if (type === 'servicesModule/toggle') { + if (type === 'services/toggle') { const selectedNodeType = payload.type as AvailableService const newStatus = services[selectedNodeType].toggleNode(payload.url, payload.active) if (newStatus) { - store.commit('servicesModule/status', { status: newStatus, serviceType: selectedNodeType }) + store.commit('services/status', { status: newStatus, serviceType: selectedNodeType }) } } }) diff --git a/src/store/modules/services/services-state.ts b/src/store/modules/services/services-state.ts index 9a78028f5..2ac6ac23f 100644 --- a/src/store/modules/services/services-state.ts +++ b/src/store/modules/services/services-state.ts @@ -1,6 +1,6 @@ import { ServicesState } from '@/store/modules/services/types.ts' export const state: ServicesState = { - rate: {}, + 'rates-info': {}, useFastestService: true } diff --git a/src/store/modules/services/types.ts b/src/store/modules/services/types.ts index ead3e9a79..d07b378c5 100644 --- a/src/store/modules/services/types.ts +++ b/src/store/modules/services/types.ts @@ -1,6 +1,6 @@ export type ServicesState = { - rate: Record + 'rates-info': Record useFastestService: true } -export type AvailableService = 'rate' +export type AvailableService = 'rates-info' From b86f8a43f6d6d2c2f7e6190935e2f504ed424a19 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 12 Jul 2024 23:58:19 +0300 Subject: [PATCH 05/23] fix(NodesTableHead): label as a property --- src/components/nodes/coins/CoinNodesTable.vue | 2 +- src/components/nodes/components/NodesTableHead.vue | 4 ++-- src/components/nodes/services/ServiceNodesTable.vue | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/nodes/coins/CoinNodesTable.vue b/src/components/nodes/coins/CoinNodesTable.vue index 8e0bb3865..fc4e7f166 100644 --- a/src/components/nodes/coins/CoinNodesTable.vue +++ b/src/components/nodes/coins/CoinNodesTable.vue @@ -1,6 +1,6 @@