diff --git a/backend/controllers/cln/payments.js b/backend/controllers/cln/payments.js index 19507405..daf3837e 100644 --- a/backend/controllers/cln/payments.js +++ b/backend/controllers/cln/payments.js @@ -108,7 +108,7 @@ export const postPayment = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body }); if (req.body.paymentType === 'OFFER') { if (req.body.saveToDB && req.body.bolt12) { - const offerToUpdate = { bolt12: req.body.bolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() }; + const offerToUpdate = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() }; if (req.body.vendor) { offerToUpdate['vendor'] = req.body.vendor; } @@ -116,7 +116,7 @@ export const postPayment = (req, res, next) => { offerToUpdate['description'] = req.body.description; } return databaseService.update(req.session.selectedNode, CollectionsEnum.OFFERS, offerToUpdate, CollectionFieldsEnum.BOLT12, req.body.bolt12).then((updatedOffer) => { - logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Updated', data: updatedOffer }); + logger.log({ level: 'DEBUG', fileName: 'Payments', msg: 'Offer Updated', data: updatedOffer }); return res.status(201).json({ paymentResponse: body, saveToDBResponse: updatedOffer }); }).catch((errDB) => { logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB update error', error: errDB }); diff --git a/backend/controllers/shared/pageSettings.js b/backend/controllers/shared/pageSettings.js new file mode 100644 index 00000000..1c80bba8 --- /dev/null +++ b/backend/controllers/shared/pageSettings.js @@ -0,0 +1,27 @@ +import { Database } from '../../utils/database.js'; +import { Logger } from '../../utils/logger.js'; +import { Common } from '../../utils/common.js'; +import { CollectionsEnum } from '../../models/database.model.js'; +const logger = Logger; +const common = Common; +const databaseService = Database; +export const getPageSettings = (req, res, next) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Getting Page Settings..' }); + databaseService.find(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS).then((settings) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Page Settings Received', data: settings }); + res.status(200).json(settings); + }).catch((errRes) => { + const err = common.handleError(errRes, 'Page Settings', 'Page Settings Error', req.session.selectedNode); + return res.status(err.statusCode).json({ message: err.message, error: err.error }); + }); +}; +export const savePageSettings = (req, res, next) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Saving Page Settings..' }); + return databaseService.insert(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS, req.updatedSettings).then((insertedSettings) => { + logger.log({ level: 'DEBUG', fileName: 'Page Settings', msg: 'Page Settings Updated', data: insertedSettings }); + return res.status(201).json(true); + }).catch((errRes) => { + const err = common.handleError(errRes, 'Page Settings', 'Page Settings Update Error', req.session.selectedNode); + return res.status(err.statusCode).json({ message: err.message, error: err.error }); + }); +}; diff --git a/backend/models/database.model.js b/backend/models/database.model.js index 0ae353ca..5f6cb97e 100644 --- a/backend/models/database.model.js +++ b/backend/models/database.model.js @@ -1,20 +1,15 @@ -export var CollectionsEnum; -(function (CollectionsEnum) { - CollectionsEnum["OFFERS"] = "Offers"; -})(CollectionsEnum || (CollectionsEnum = {})); export var OfferFieldsEnum; (function (OfferFieldsEnum) { OfferFieldsEnum["BOLT12"] = "bolt12"; - OfferFieldsEnum["AMOUNTMSAT"] = "amountmSat"; + OfferFieldsEnum["AMOUNTMSAT"] = "amountMSat"; OfferFieldsEnum["TITLE"] = "title"; OfferFieldsEnum["VENDOR"] = "vendor"; OfferFieldsEnum["DESCRIPTION"] = "description"; })(OfferFieldsEnum || (OfferFieldsEnum = {})); -export const CollectionFieldsEnum = Object.assign({}, OfferFieldsEnum); export class Offer { - constructor(bolt12, amountmSat, title, vendor, description, lastUpdatedAt) { + constructor(bolt12, amountMSat, title, vendor, description, lastUpdatedAt) { this.bolt12 = bolt12; - this.amountmSat = amountmSat; + this.amountMSat = amountMSat; this.title = title; this.vendor = vendor; this.description = description; @@ -23,16 +18,76 @@ export class Offer { } export const validateOffer = (documentToValidate) => { if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.BOLT12)) { - return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + 'is mandatory.' }); + return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + ' is mandatory.' }); } if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.AMOUNTMSAT)) { - return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'is mandatory.' }); + return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + ' is mandatory.' }); } if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TITLE)) { - return ({ isValid: false, error: CollectionFieldsEnum.TITLE + 'is mandatory.' }); + return ({ isValid: false, error: CollectionFieldsEnum.TITLE + ' is mandatory.' }); } if ((typeof documentToValidate[CollectionFieldsEnum.AMOUNTMSAT] !== 'number')) { - return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'should be a number.' }); + return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + ' should be a number.' }); + } + return ({ isValid: true }); +}; +export var SortOrderEnum; +(function (SortOrderEnum) { + SortOrderEnum["ASCENDING"] = "Ascending"; + SortOrderEnum["DESCENDING"] = "Descending"; +})(SortOrderEnum || (SortOrderEnum = {})); +export var PageSettingsFieldsEnum; +(function (PageSettingsFieldsEnum) { + PageSettingsFieldsEnum["PAYMENTS"] = "payments"; + PageSettingsFieldsEnum["INVOICES"] = "invoices"; + PageSettingsFieldsEnum["TABLES"] = "tables"; +})(PageSettingsFieldsEnum || (PageSettingsFieldsEnum = {})); +export var TableSettingsFieldsEnum; +(function (TableSettingsFieldsEnum) { + TableSettingsFieldsEnum["TABLE_ID"] = "tableId"; + TableSettingsFieldsEnum["RECORDS_PER_PAGE"] = "recordsPerPage"; + TableSettingsFieldsEnum["SORT_BY"] = "sortBy"; + TableSettingsFieldsEnum["SORT_ORDER"] = "sortOrder"; + TableSettingsFieldsEnum["SHOW_COLUMNS"] = "showColumns"; +})(TableSettingsFieldsEnum || (TableSettingsFieldsEnum = {})); +export class TableSetting { + constructor(tableId, recordsPerPage, sortBy, sortOrder, showColumns) { + this.tableId = tableId; + this.recordsPerPage = recordsPerPage; + this.sortBy = sortBy; + this.sortOrder = sortOrder; + this.showColumns = showColumns; + } +} +export class PageSettings { + constructor(pages) { + this.pages = pages; + } +} +export const validatePageSettings = (documentToValidate) => { + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.PAYMENTS)) { + return ({ isValid: false, error: CollectionFieldsEnum.PAYMENTS + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.INVOICES)) { + return ({ isValid: false, error: CollectionFieldsEnum.INVOICES + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TABLES)) { + return ({ isValid: false, error: CollectionFieldsEnum.TABLES + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TABLE_ID)) { + return ({ isValid: false, error: CollectionFieldsEnum.TABLE_ID + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.SHOW_COLUMNS)) { + return ({ isValid: false, error: CollectionFieldsEnum.SHOW_COLUMNS + ' is mandatory.' }); + } + if (documentToValidate[CollectionFieldsEnum.SHOW_COLUMNS].length < 3) { + return ({ isValid: false, error: CollectionFieldsEnum.SHOW_COLUMNS + ' should have at least 2 fields.' }); } return ({ isValid: true }); }; +export var CollectionsEnum; +(function (CollectionsEnum) { + CollectionsEnum["OFFERS"] = "Offers"; + CollectionsEnum["PAGE_SETTINGS"] = "PageSettings"; +})(CollectionsEnum || (CollectionsEnum = {})); +export const CollectionFieldsEnum = Object.assign(Object.assign(Object.assign({}, OfferFieldsEnum), PageSettingsFieldsEnum), TableSettingsFieldsEnum); diff --git a/backend/routes/shared/index.js b/backend/routes/shared/index.js index 9a4f58f6..2fd7e10e 100644 --- a/backend/routes/shared/index.js +++ b/backend/routes/shared/index.js @@ -4,12 +4,14 @@ import authenticateRoutes from './authenticate.js'; import boltzRoutes from './boltz.js'; import loopRoutes from './loop.js'; import RTLConfRoutes from './RTLConf.js'; +import pageSettingsRoutes from './pageSettings.js'; const router = Router(); const sharedRoutes = [ { path: '/authenticate', route: authenticateRoutes }, { path: '/boltz', route: boltzRoutes }, { path: '/loop', route: loopRoutes }, - { path: '/conf', route: RTLConfRoutes } + { path: '/conf', route: RTLConfRoutes }, + { path: '/pagesettings', route: pageSettingsRoutes } ]; sharedRoutes.forEach((route) => { router.use(route.path, route.route); diff --git a/backend/routes/shared/pageSettings.js b/backend/routes/shared/pageSettings.js new file mode 100644 index 00000000..6f3920c6 --- /dev/null +++ b/backend/routes/shared/pageSettings.js @@ -0,0 +1,8 @@ +import exprs from 'express'; +const { Router } = exprs; +import { isAuthenticated } from '../../utils/authCheck.js'; +import { getPageSettings, savePageSettings } from '../../controllers/shared/pageSettings.js'; +const router = Router(); +router.get('/', isAuthenticated, getPageSettings); +router.post('/', isAuthenticated, savePageSettings); +export default router; diff --git a/server/controllers/cln/payments.ts b/server/controllers/cln/payments.ts index 692ac09c..e141a296 100644 --- a/server/controllers/cln/payments.ts +++ b/server/controllers/cln/payments.ts @@ -95,11 +95,11 @@ export const postPayment = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body }); if (req.body.paymentType === 'OFFER') { if (req.body.saveToDB && req.body.bolt12) { - const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() }; + const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() }; if (req.body.vendor) { offerToUpdate['vendor'] = req.body.vendor; } if (req.body.description) { offerToUpdate['description'] = req.body.description; } return databaseService.update(req.session.selectedNode, CollectionsEnum.OFFERS, offerToUpdate, CollectionFieldsEnum.BOLT12, req.body.bolt12).then((updatedOffer) => { - logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Updated', data: updatedOffer }); + logger.log({ level: 'DEBUG', fileName: 'Payments', msg: 'Offer Updated', data: updatedOffer }); return res.status(201).json({ paymentResponse: body, saveToDBResponse: updatedOffer }); }).catch((errDB) => { logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB update error', error: errDB }); diff --git a/server/controllers/shared/pageSettings.ts b/server/controllers/shared/pageSettings.ts new file mode 100644 index 00000000..6f9ccf4c --- /dev/null +++ b/server/controllers/shared/pageSettings.ts @@ -0,0 +1,30 @@ +import { Database, DatabaseService } from '../../utils/database.js'; +import { Logger, LoggerService } from '../../utils/logger.js'; +import { Common, CommonService } from '../../utils/common.js'; +import { CollectionsEnum, PageSettings } from '../../models/database.model.js'; + +const logger: LoggerService = Logger; +const common: CommonService = Common; +const databaseService: DatabaseService = Database; + +export const getPageSettings = (req, res, next) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Getting Page Settings..' }); + databaseService.find(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS).then((settings: PageSettings) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Page Settings Received', data: settings }); + res.status(200).json(settings); + }).catch((errRes) => { + const err = common.handleError(errRes, 'Page Settings', 'Page Settings Error', req.session.selectedNode); + return res.status(err.statusCode).json({ message: err.message, error: err.error }); + }); +}; + +export const savePageSettings = (req, res, next) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Saving Page Settings..' }); + return databaseService.insert(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS, req.updatedSettings).then((insertedSettings) => { + logger.log({ level: 'DEBUG', fileName: 'Page Settings', msg: 'Page Settings Updated', data: insertedSettings }); + return res.status(201).json(true); + }).catch((errRes) => { + const err = common.handleError(errRes, 'Page Settings', 'Page Settings Update Error', req.session.selectedNode); + return res.status(err.statusCode).json({ message: err.message, error: err.error }); + }); +}; diff --git a/server/models/database.model.ts b/server/models/database.model.ts index 388bbb81..f36e365f 100644 --- a/server/models/database.model.ts +++ b/server/models/database.model.ts @@ -1,26 +1,16 @@ -export enum CollectionsEnum { - OFFERS = 'Offers' -} - -export type Collections = { - Offers: Offer[]; -} - export enum OfferFieldsEnum { BOLT12 = 'bolt12', - AMOUNTMSAT = 'amountmSat', + AMOUNTMSAT = 'amountMSat', TITLE = 'title', VENDOR = 'vendor', DESCRIPTION = 'description' } -export const CollectionFieldsEnum = { ...OfferFieldsEnum }; - export class Offer { constructor( public bolt12: string, - public amountmSat: number, + public amountMSat: number, public title: string, public vendor?: string, public description?: string, @@ -31,16 +21,92 @@ export class Offer { export const validateOffer = (documentToValidate): any => { if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.BOLT12)) { - return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + 'is mandatory.' }); + return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + ' is mandatory.' }); } if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.AMOUNTMSAT)) { - return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'is mandatory.' }); + return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + ' is mandatory.' }); } if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TITLE)) { - return ({ isValid: false, error: CollectionFieldsEnum.TITLE + 'is mandatory.' }); + return ({ isValid: false, error: CollectionFieldsEnum.TITLE + ' is mandatory.' }); } if ((typeof documentToValidate[CollectionFieldsEnum.AMOUNTMSAT] !== 'number')) { - return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'should be a number.' }); + return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + ' should be a number.' }); } return ({ isValid: true }); }; + +export enum SortOrderEnum { + ASCENDING = 'Ascending', + DESCENDING = 'Descending' +} + +export enum PageSettingsFieldsEnum { + PAYMENTS = 'payments', + INVOICES = 'invoices', + TABLES = 'tables' +} + +export enum TableSettingsFieldsEnum { + TABLE_ID = 'tableId', + RECORDS_PER_PAGE = 'recordsPerPage', + SORT_BY = 'sortBy', + SORT_ORDER = 'sortOrder', + SHOW_COLUMNS = 'showColumns' +} + +export class TableSetting { + + constructor( + public tableId: string, + public recordsPerPage?: number, + public sortBy?: string, + public sortOrder?: SortOrderEnum, + public showColumns?: any[] + ) { } + +} + +export class PageSettings { + + constructor( + public pages: { + payments: { tables: TableSetting[] }; + invoices: { tables: TableSetting[] }; + } + ) { } + +} + +export const validatePageSettings = (documentToValidate): any => { + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.PAYMENTS)) { + return ({ isValid: false, error: CollectionFieldsEnum.PAYMENTS + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.INVOICES)) { + return ({ isValid: false, error: CollectionFieldsEnum.INVOICES + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TABLES)) { + return ({ isValid: false, error: CollectionFieldsEnum.TABLES + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TABLE_ID)) { + return ({ isValid: false, error: CollectionFieldsEnum.TABLE_ID + ' is mandatory.' }); + } + if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.SHOW_COLUMNS)) { + return ({ isValid: false, error: CollectionFieldsEnum.SHOW_COLUMNS + ' is mandatory.' }); + } + if (documentToValidate[CollectionFieldsEnum.SHOW_COLUMNS].length < 3) { + return ({ isValid: false, error: CollectionFieldsEnum.SHOW_COLUMNS + ' should have at least 2 fields.' }); + } + return ({ isValid: true }); +}; + +export enum CollectionsEnum { + OFFERS = 'Offers', + PAGE_SETTINGS = 'PageSettings' +} + +export type Collections = { + Offers: Offer[]; + PageSettings: PageSettings; +} + +export const CollectionFieldsEnum = { ...OfferFieldsEnum, ...PageSettingsFieldsEnum, ...TableSettingsFieldsEnum }; diff --git a/server/routes/shared/index.ts b/server/routes/shared/index.ts index fce3c789..234153f9 100644 --- a/server/routes/shared/index.ts +++ b/server/routes/shared/index.ts @@ -4,6 +4,7 @@ import authenticateRoutes from './authenticate.js'; import boltzRoutes from './boltz.js'; import loopRoutes from './loop.js'; import RTLConfRoutes from './RTLConf.js'; +import pageSettingsRoutes from './pageSettings.js'; const router = Router(); @@ -11,7 +12,8 @@ const sharedRoutes = [ { path: '/authenticate', route: authenticateRoutes }, { path: '/boltz', route: boltzRoutes }, { path: '/loop', route: loopRoutes }, - { path: '/conf', route: RTLConfRoutes } + { path: '/conf', route: RTLConfRoutes }, + { path: '/pagesettings', route: pageSettingsRoutes } ]; sharedRoutes.forEach((route) => { diff --git a/server/routes/shared/pageSettings.ts b/server/routes/shared/pageSettings.ts new file mode 100644 index 00000000..dc2ccc56 --- /dev/null +++ b/server/routes/shared/pageSettings.ts @@ -0,0 +1,11 @@ +import exprs from 'express'; +const { Router } = exprs; +import { isAuthenticated } from '../../utils/authCheck.js'; +import { getPageSettings, savePageSettings } from '../../controllers/shared/pageSettings.js'; + +const router = Router(); + +router.get('/', isAuthenticated, getPageSettings); +router.post('/', isAuthenticated, savePageSettings); + +export default router; diff --git a/src/app/cln/store/cln.actions.ts b/src/app/cln/store/cln.actions.ts index 2f055743..00fb1727 100644 --- a/src/app/cln/store/cln.actions.ts +++ b/src/app/cln/store/cln.actions.ts @@ -4,6 +4,7 @@ import { CLNActions } from '../../shared/services/consts-enums-functions'; import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload'; import { SelNodeChild } from '../../shared/models/RTLconfig'; import { GetInfo, Fees, Peer, Payment, QueryRoutes, Channel, FeeRates, Invoice, ListInvoices, OnChain, UTXO, SaveChannel, GetNewAddress, DetachPeer, UpdateChannel, CloseChannel, SendPayment, GetQueryRoutes, ChannelLookup, OfferInvoice, Offer, OfferBookmark, ListForwards, FetchListForwards, LocalFailedEvent, ForwardingEvent } from '../../shared/models/clnModels'; +import { PageSettingsCLN } from '../../shared/models/pageSettings'; export const updateCLAPICallStatus = createAction(CLNActions.UPDATE_API_CALL_STATUS_CLN, props<{ payload: ApiCallStatusPayload }>()); @@ -11,6 +12,12 @@ export const resetCLStore = createAction(CLNActions.RESET_CLN_STORE, props<{ pay export const setChildNodeSettingsCL = createAction(CLNActions.SET_CHILD_NODE_SETTINGS_CLN, props<{ payload: SelNodeChild }>()); +export const fetchPageSettingsCL = createAction(CLNActions.FETCH_PAGE_SETTINGS_CLN); + +export const setPageSettings = createAction(CLNActions.SET_PAGE_SETTINGS_CLN, props<{ payload: PageSettingsCLN }>()); + +export const savePageSettings = createAction(CLNActions.SAVE_PAGE_SETTINGS_CLN, props<{ payload: PageSettingsCLN }>()); + export const fetchInfoCL = createAction(CLNActions.FETCH_INFO_CLN, props<{ payload: { loadPage: string } }>()); export const setInfo = createAction(CLNActions.SET_INFO_CLN, props<{ payload: GetInfo }>()); diff --git a/src/app/cln/store/cln.effects.ts b/src/app/cln/store/cln.effects.ts index 57828d7c..67e9257b 100644 --- a/src/app/cln/store/cln.effects.ts +++ b/src/app/cln/store/cln.effects.ts @@ -14,13 +14,13 @@ import { SessionService } from '../../shared/services/session.service'; import { WebSocketClientService } from '../../shared/services/web-socket.service'; import { ErrorMessageComponent } from '../../shared/components/data-modal/error-message/error-message.component'; import { CLNInvoiceInformationComponent } from '../transactions/invoices/invoice-information-modal/invoice-information.component'; -import { GetInfo, Fees, Balance, LocalRemoteBalance, Payment, FeeRates, ListInvoices, Invoice, Peer, OnChain, QueryRoutes, SaveChannel, GetNewAddress, DetachPeer, UpdateChannel, CloseChannel, SendPayment, GetQueryRoutes, ChannelLookup, FetchInvoices, Channel, OfferInvoice, Offer, ListForwards, FetchListForwards, ForwardingEvent, LocalFailedEvent } from '../../shared/models/clnModels'; +import { GetInfo, Fees, Balance, LocalRemoteBalance, Payment, FeeRates, ListInvoices, Invoice, Peer, OnChain, QueryRoutes, SaveChannel, GetNewAddress, DetachPeer, UpdateChannel, CloseChannel, SendPayment, GetQueryRoutes, ChannelLookup, FetchInvoices, Channel, OfferInvoice, Offer } from '../../shared/models/clnModels'; import { AlertTypeEnum, APICallStatusEnum, UI_MESSAGES, CLNWSEventTypeEnum, CLNActions, RTLActions, CLNForwardingEventsStatusEnum } from '../../shared/services/consts-enums-functions'; import { closeAllDialogs, closeSpinner, logout, openAlert, openSnackBar, openSpinner, setApiUrl, setNodeData } from '../../store/rtl.actions'; import { RTLState } from '../../store/rtl.state'; -import { addUpdateOfferBookmark, fetchBalance, fetchChannels, fetchFeeRates, fetchFees, fetchInvoices, fetchLocalRemoteBalance, fetchPayments, fetchPeers, fetchUTXOs, getForwardingHistory, setLookup, setPeers, setQueryRoutes, updateCLAPICallStatus, updateInvoice, setOfferInvoice, sendPaymentStatus, setForwardingHistory } from './cln.actions'; -import { allAPIsCallStatus, clnNodeInformation } from './cln.selector'; +import { addUpdateOfferBookmark, fetchBalance, fetchChannels, fetchFeeRates, fetchFees, fetchInvoices, fetchLocalRemoteBalance, fetchPayments, fetchPeers, fetchUTXOs, setLookup, setPeers, setQueryRoutes, updateCLAPICallStatus, updateInvoice, setOfferInvoice, sendPaymentStatus, setForwardingHistory, fetchPageSettingsCL } from './cln.actions'; +import { allAPIsCallStatus } from './cln.selector'; import { ApiCallsListCL } from '../../shared/models/apiCallsPayload'; import { CLNOfferInformationComponent } from '../transactions/offers/offer-information-modal/offer-information.component'; @@ -934,6 +934,51 @@ export class CLNEffects implements OnDestroy { }) )); + pageSettingsFetchCL = createEffect(() => this.actions.pipe( + ofType(CLNActions.FETCH_PAGE_SETTINGS_CLN), + mergeMap(() => { + this.store.dispatch(updateCLAPICallStatus({ payload: { action: 'FetchPageSettings', status: APICallStatusEnum.INITIATED } })); + return this.httpClient.get(environment.PAGE_SETTINGS_API).pipe( + map((pageSettings: any) => { + this.logger.info(pageSettings); + this.store.dispatch(updateCLAPICallStatus({ payload: { action: 'FetchPageSettings', status: APICallStatusEnum.COMPLETED } })); + return { + type: CLNActions.SET_PAGE_SETTINGS_CLN, + payload: pageSettings + }; + }), + catchError((err: any) => { + this.handleErrorWithoutAlert('FetchPageSettings', UI_MESSAGES.NO_SPINNER, 'Fetching Page Settings Failed.', err); + return of({ type: RTLActions.VOID }); + }) + ); + }) + )); + + savePageSettingsCL = createEffect(() => this.actions.pipe( + ofType(CLNActions.SAVE_PAGE_SETTINGS_CLN), + mergeMap((action: { type: string, payload: any }) => { + this.store.dispatch(openSpinner({ payload: UI_MESSAGES.UPDATE_PAGE_SETTINGS })); + this.store.dispatch(updateCLAPICallStatus({ payload: { action: 'SavePageSettings', status: APICallStatusEnum.INITIATED } })); + return this.httpClient.post(environment.PAGE_SETTINGS_API, action.payload). + pipe( + map((postRes: any) => { + this.logger.info(postRes); + this.store.dispatch(updateCLAPICallStatus({ payload: { action: 'SavePageSettings', status: APICallStatusEnum.COMPLETED } })); + this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.UPDATE_PAGE_SETTINGS })); + return { + type: CLNActions.SET_PAGE_SETTINGS_CLN, + payload: postRes + }; + }), + catchError((err: any) => { + this.handleErrorWithoutAlert('SavePageSettings', UI_MESSAGES.UPDATE_PAGE_SETTINGS, 'Page Settings Update Failed.', err); + return of({ type: RTLActions.VOID }); + }) + ); + }) + )); + initializeRemainingData(info: any, landingPage: string) { this.sessionService.setItem('clUnlocked', 'true'); const node_data = { @@ -958,6 +1003,7 @@ export class CLNEffects implements OnDestroy { newRoute = '/cln/home'; } this.router.navigate([newRoute]); + this.store.dispatch(fetchPageSettingsCL()); this.store.dispatch(fetchInvoices({ payload: { num_max_invoices: 1000000, index_offset: 0, reversed: true } })); this.store.dispatch(fetchFees()); this.store.dispatch(fetchChannels()); diff --git a/src/app/cln/store/cln.reducers.ts b/src/app/cln/store/cln.reducers.ts index eb53da5e..2b36ed30 100644 --- a/src/app/cln/store/cln.reducers.ts +++ b/src/app/cln/store/cln.reducers.ts @@ -4,10 +4,11 @@ import { addInvoice, addPeer, removeChannel, removePeer, resetCLStore, setBalance, setChannels, setChildNodeSettingsCL, setFeeRates, setFees, setForwardingHistory, setInfo, setInvoices, setLocalRemoteBalance, setOffers, addOffer, setPayments, setPeers, setUTXOs, - updateCLAPICallStatus, updateInvoice, updateOffer, setOfferBookmarks, addUpdateOfferBookmark, removeOfferBookmark + updateCLAPICallStatus, updateInvoice, updateOffer, setOfferBookmarks, addUpdateOfferBookmark, removeOfferBookmark, setPageSettings } from './cln.actions'; import { Channel, OfferBookmark } from '../../shared/models/clnModels'; -import { CLNForwardingEventsStatusEnum } from '../../shared/services/consts-enums-functions'; +import { CLNForwardingEventsStatusEnum, CLN_DEFAULT_PAGE_SETTINGS } from '../../shared/services/consts-enums-functions'; +import { PageSettingsCLN } from '../../shared/models/pageSettings'; export const CLNReducer = createReducer(initCLNState, on(updateCLAPICallStatus, (state, { payload }) => { @@ -195,7 +196,7 @@ export const CLNReducer = createReducer(initCLNState, } else { const updatedOffer = { ...newOfferBMs[offerBMExistsIdx] }; updatedOffer.title = payload.title; - updatedOffer.amountmSat = payload.amountmSat; + updatedOffer.amountMSat = payload.amountMSat; updatedOffer.lastUpdatedAt = payload.lastUpdatedAt; updatedOffer.description = payload.description; updatedOffer.vendor = payload.vendor; @@ -216,6 +217,16 @@ export const CLNReducer = createReducer(initCLNState, ...state, offersBookmarks: modifiedOfferBookmarks }; + }), + on(setPageSettings, (state, { payload }) => { + const sortedPageSettings = Object.keys(CLN_DEFAULT_PAGE_SETTINGS).reduce((acc, page) => { + acc[page] = (payload && payload.hasOwnProperty(page)) ? payload[page] : CLN_DEFAULT_PAGE_SETTINGS[page]; + return acc; + }, {}); + return { + ...state, + pageSettings: sortedPageSettings + }; }) ); diff --git a/src/app/cln/store/cln.selector.ts b/src/app/cln/store/cln.selector.ts index e1b9e6d9..605ced46 100644 --- a/src/app/cln/store/cln.selector.ts +++ b/src/app/cln/store/cln.selector.ts @@ -4,6 +4,7 @@ import { CLNState } from './cln.state'; export const clnState = createFeatureSelector('cln'); export const clnNodeSettings = createSelector(clnState, (state: CLNState) => state.nodeSettings); +export const clnPageSettings = createSelector(clnState, (state: CLNState) => ({ pageSettings: state.pageSettings, apiCallStatus: state.apisCallStatus.FetchPageSettings })); export const clnNodeInformation = createSelector(clnState, (state: CLNState) => state.information); export const apiCallStatusNodeInfo = createSelector(clnState, (state: CLNState) => state.apisCallStatus.FetchInfo); export const allAPIsCallStatus = createSelector(clnState, (state: CLNState) => state.apisCallStatus); diff --git a/src/app/cln/store/cln.state.ts b/src/app/cln/store/cln.state.ts index ddd05b0d..20ccf2f7 100644 --- a/src/app/cln/store/cln.state.ts +++ b/src/app/cln/store/cln.state.ts @@ -1,11 +1,13 @@ import { SelNodeChild } from '../../shared/models/RTLconfig'; -import { APICallStatusEnum, UserPersonaEnum } from '../../shared/services/consts-enums-functions'; +import { APICallStatusEnum, CLN_DEFAULT_PAGE_SETTINGS, UserPersonaEnum } from '../../shared/services/consts-enums-functions'; import { GetInfo, Fees, Balance, LocalRemoteBalance, Peer, Payment, Channel, FeeRates, ListInvoices, UTXO, Offer, OfferBookmark, ListForwards } from '../../shared/models/clnModels'; import { ApiCallsListCL } from '../../shared/models/apiCallsPayload'; +import { PageSettingsCLN } from '../../shared/models/pageSettings'; export interface CLNState { apisCallStatus: ApiCallsListCL; nodeSettings: SelNodeChild | null; + pageSettings: PageSettingsCLN; information: GetInfo; fees: Fees; feeRatesPerKB: FeeRates; @@ -28,6 +30,7 @@ export interface CLNState { export const initCLNState: CLNState = { apisCallStatus: { + FetchPageSettings: { status: APICallStatusEnum.UN_INITIATED }, FetchInfo: { status: APICallStatusEnum.UN_INITIATED }, FetchInvoices: { status: APICallStatusEnum.UN_INITIATED }, FetchFees: { status: APICallStatusEnum.UN_INITIATED }, @@ -46,6 +49,7 @@ export const initCLNState: CLNState = { FetchOfferBookmarks: { status: APICallStatusEnum.UN_INITIATED } }, nodeSettings: { userPersona: UserPersonaEnum.OPERATOR, selCurrencyUnit: 'USD', fiatConversion: false, channelBackupPath: '', currencyUnits: [], enableOffers: false, enablePeerswap: false }, + pageSettings: CLN_DEFAULT_PAGE_SETTINGS, information: {}, fees: {}, feeRatesPerKB: {}, diff --git a/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.html b/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.html index 51fba973..5a17bd84 100644 --- a/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.html +++ b/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.html @@ -25,9 +25,9 @@ - + Amount (Sats) - {{(offersbookmark.amountmSat === 0) ? 'Open' : (offersbookmark.amountmSat / 1000) | number}} + {{(offersbookmark.amountMSat === 0) ? 'Open' : (offersbookmark.amountMSat / 1000) | number}} Description diff --git a/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.ts b/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.ts index f976e883..1f3e1154 100644 --- a/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.ts +++ b/src/app/cln/transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component.ts @@ -52,16 +52,16 @@ export class CLNOfferBookmarksTableComponent implements OnInit, AfterViewInit, O this.screenSize = this.commonService.getScreenSize(); if (this.screenSize === ScreenSizeEnum.XS) { this.flgSticky = false; - this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'actions']; + this.displayedColumns = ['lastUpdatedAt', 'title', 'amountMSat', 'actions']; } else if (this.screenSize === ScreenSizeEnum.SM) { this.flgSticky = false; - this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'actions']; + this.displayedColumns = ['lastUpdatedAt', 'title', 'amountMSat', 'actions']; } else if (this.screenSize === ScreenSizeEnum.MD) { this.flgSticky = false; - this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'description', 'actions']; + this.displayedColumns = ['lastUpdatedAt', 'title', 'amountMSat', 'description', 'actions']; } else { this.flgSticky = true; - this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'description', 'actions']; + this.displayedColumns = ['lastUpdatedAt', 'title', 'amountMSat', 'description', 'actions']; } } diff --git a/src/app/shared/components/node-config/page-settings/page-settings.component.html b/src/app/shared/components/node-config/page-settings/page-settings.component.html index 649a27a8..fe9ad8ed 100644 --- a/src/app/shared/components/node-config/page-settings/page-settings.component.html +++ b/src/app/shared/components/node-config/page-settings/page-settings.component.html @@ -4,36 +4,36 @@ Page Settings - + - {{page.pageId | titlecase}} + {{page.key | titlecase}} -
+
- + {{pageSizeOption}} - - + + {{field | camelcaseWithReplace:'_'}} - + {{so}} - - + + {{field | camelcaseWithReplace:'_'}} diff --git a/src/app/shared/components/node-config/page-settings/page-settings.component.ts b/src/app/shared/components/node-config/page-settings/page-settings.component.ts index ebc2effd..af2abf5e 100644 --- a/src/app/shared/components/node-config/page-settings/page-settings.component.ts +++ b/src/app/shared/components/node-config/page-settings/page-settings.component.ts @@ -4,17 +4,12 @@ import { takeUntil } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { faPenRuler } from '@fortawesome/free-solid-svg-icons'; -import { PAGE_SIZE_OPTIONS, ScreenSizeEnum, UI_MESSAGES, SORT_ORDERS } from '../../../services/consts-enums-functions'; -import { ConfigSettingsNode } from '../../../models/RTLconfig'; +import { CLN_TABLE_FIELDS_DEF, PAGE_SIZE_OPTIONS, ScreenSizeEnum, SORT_ORDERS } from '../../../services/consts-enums-functions'; import { LoggerService } from '../../../services/logger.service'; import { CommonService } from '../../../services/common.service'; import { RTLState } from '../../../../store/rtl.state'; -import { saveSettings } from '../../../../store/rtl.actions'; -import { setChildNodeSettingsECL } from '../../../../eclair/store/ecl.actions'; -import { setChildNodeSettingsCL } from '../../../../cln/store/cln.actions'; -import { setChildNodeSettingsLND } from '../../../../lnd/store/lnd.actions'; -import { rootSelectedNode } from '../../../../store/rtl.selector'; -import { RTL_PAGE_SETTINGS } from '../../../models/pageSettings'; +import { TableSetting, PageSettingsCLN } from '../../../models/pageSettings'; +import { clnPageSettings } from '../../../../cln/store/cln.selector'; @Component({ selector: 'rtl-page-settings', @@ -24,11 +19,11 @@ import { RTL_PAGE_SETTINGS } from '../../../models/pageSettings'; export class PageSettingsComponent implements OnInit, OnDestroy { public faPenRuler = faPenRuler; - public selNode: ConfigSettingsNode | any; public screenSize = ''; public screenSizeEnum = ScreenSizeEnum; public pageSizeOptions = PAGE_SIZE_OPTIONS; - public pageSettings = null; + public pageSettings: PageSettingsCLN | null = null; + public tableFieldsDef = CLN_TABLE_FIELDS_DEF; public sortOrders = SORT_ORDERS; unSubs: Array> = [new Subject(), new Subject()]; @@ -37,14 +32,18 @@ export class PageSettingsComponent implements OnInit, OnDestroy { } ngOnInit() { - this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[0])).subscribe((selNode) => { - this.selNode = selNode; - this.pageSettings = RTL_PAGE_SETTINGS[this.selNode.lnImplementation.toUpperCase()].sort((x, y) => ((x.seq < y.seq) ? -1 : ((x.seq > y.seq) ? 1 : 0))); - this.logger.info(selNode); - this.logger.warn(this.pageSettings); + this.store.select(clnPageSettings).pipe(takeUntil(this.unSubs[0])).subscribe((settings) => { + this.pageSettings = settings.pageSettings; + this.logger.info(settings); }); } + onShowColumnsChange(table: TableSetting) { + if (table.showColumns && !table.showColumns.includes(table.sortBy)) { + table.sortBy = table.showColumns[0]; + } + } + onUpdatePageSettings() { // if (this.selNode.settings.fiatConversion && !this.selNode.settings.currencyUnit) { // return true; diff --git a/src/app/shared/models/apiCallsPayload.ts b/src/app/shared/models/apiCallsPayload.ts index 024ed6a7..85a41b3d 100644 --- a/src/app/shared/models/apiCallsPayload.ts +++ b/src/app/shared/models/apiCallsPayload.ts @@ -48,6 +48,7 @@ export interface ApiCallsListCL { FetchBalance: ApiCallStatusPayload; FetchLocalRemoteBalance: ApiCallStatusPayload; // Non-initial calls + FetchPageSettings: ApiCallStatusPayload; FetchInvoices: ApiCallStatusPayload; FetchFeeRatesperkb: ApiCallStatusPayload; FetchFeeRatesperkw: ApiCallStatusPayload; diff --git a/src/app/shared/models/clnModels.ts b/src/app/shared/models/clnModels.ts index d166aae7..50490bad 100644 --- a/src/app/shared/models/clnModels.ts +++ b/src/app/shared/models/clnModels.ts @@ -103,7 +103,7 @@ export interface Offer { export interface OfferBookmark { lastUpdatedAt?: string; bolt12?: string; - amountmSat?: number; + amountMSat?: number; title?: string; vendor?: string; description?: string; diff --git a/src/app/shared/models/pageSettings.ts b/src/app/shared/models/pageSettings.ts index d8644433..c4eeef93 100644 --- a/src/app/shared/models/pageSettings.ts +++ b/src/app/shared/models/pageSettings.ts @@ -7,39 +7,13 @@ export class TableSetting { sortBy?: string; sortOrder?: SortOrderEnum; showColumns?: any[]; - fieldsDef?: any[]; } -export class NodePageSettings { +export class PageSettingsCLN { - seq: number; - pageId: string; - tables: TableSetting[]; + payments?: { seq?: number, tables?: TableSetting[] }; + invoices?: { seq?: number, tables?: TableSetting[] }; + utxos?: { seq?: number, tables?: TableSetting[] }; } - -export class RTLPageSettings { - - LND?: NodePageSettings[]; - CLN?: NodePageSettings[]; - ECL?: NodePageSettings[]; - -} - -export const RTL_PAGE_SETTINGS: RTLPageSettings = { - LND: [], - CLN: [ - { seq: 1, pageId: 'payments', tables: [{ - tableId: 'payments', recordsPerPage: 25, sortBy: 'created_at', sortOrder: SortOrderEnum.DESCENDING, - showColumns: ['created_at', 'type', 'payment_hash', 'msatoshi_sent', 'msatoshi'], - fieldsDef: ['created_at', 'type', 'payment_hash', 'msatoshi_sent', 'msatoshi', 'amount_msat', 'amount_sent_msat', 'destination', 'status', 'memo'] - }] }, - { seq: 2, pageId: 'invoices', tables: [{ - tableId: 'invoices', recordsPerPage: 10, sortBy: 'expires_at', sortOrder: SortOrderEnum.ASCENDING, - showColumns: ['expires_at', 'paid_at', 'type', 'description', 'msatoshi', 'msatoshi_received'], - fieldsDef: ['expires_at', 'paid_at', 'type', 'description', 'msatoshi', 'msatoshi_received', 'label', 'payment_hash', 'amount_msat', 'status', 'amount_received_msat'] - }] } - ], - ECL: [] -}; diff --git a/src/app/shared/services/consts-enums-functions.ts b/src/app/shared/services/consts-enums-functions.ts index 552ec0bf..7be8a70f 100644 --- a/src/app/shared/services/consts-enums-functions.ts +++ b/src/app/shared/services/consts-enums-functions.ts @@ -1,4 +1,5 @@ import { MatPaginatorIntl } from '@angular/material/paginator'; +import { PageSettingsCLN } from '../models/pageSettings'; export function getPaginatorLabel(field: string) { const appPaginator = new MatPaginatorIntl(); @@ -323,6 +324,9 @@ export const UI_MESSAGES = { GET_FUNDER_POLICY: 'Getting Or Updating Funder Policy...', GET_LIST_CONFIGS: 'Getting Configurations List...', LIST_NETWORK_NODES: 'Getting Network Nodes List...', + GET_PAGE_SETTINGS: 'Getting Page Settings...', + SET_PAGE_SETTINGS: 'Setting Page Settings...', + UPDATE_PAGE_SETTINGS: 'Updating Page Settings...', LOG_OUT: 'Logging Out...' }; @@ -456,6 +460,9 @@ export enum CLNActions { RESET_CLN_STORE = 'RESET_CLN_STORE', UPDATE_API_CALL_STATUS_CLN = 'UPDATE_API_CALL_STATUS_CLN', SET_CHILD_NODE_SETTINGS_CLN = 'SET_CHILD_NODE_SETTINGS_CLN', + FETCH_PAGE_SETTINGS_CLN = 'FETCH_PAGE_SETTINGS_CLN', + SET_PAGE_SETTINGS_CLN = 'SET_PAGE_SETTINGS_CLN', + SAVE_PAGE_SETTINGS_CLN = 'SAVE_PAGE_SETTINGS_CLN', FETCH_INFO_CLN = 'FETCH_INFO_CL_CLN', SET_INFO_CLN = 'SET_INFO_CLN', FETCH_FEES_CLN = 'FETCH_FEES_CLN', @@ -666,3 +673,19 @@ export enum SortOrderEnum { } export const SORT_ORDERS = ['Ascending', 'Descending']; + +export const CLN_DEFAULT_PAGE_SETTINGS: PageSettingsCLN = { + payments: { seq: 1, tables: [{ tableId: 'payments', recordsPerPage: 10, sortBy: 'created_at', sortOrder: SortOrderEnum.DESCENDING, + showColumns: ['created_at', 'type', 'payment_hash', 'msatoshi_sent', 'msatoshi'] }] }, + invoices: { seq: 2, tables: [{ tableId: 'invoices', recordsPerPage: 10, sortBy: 'expires_at', sortOrder: SortOrderEnum.DESCENDING, + showColumns: ['expires_at', 'paid_at', 'type', 'description', 'msatoshi', 'msatoshi_received'] }] }, + utxos: { seq: 3, tables: [{ tableId: 'utxos', recordsPerPage: 10, sortBy: 'expires_at', sortOrder: SortOrderEnum.DESCENDING, + showColumns: ['expires_at', 'paid_at', 'type', 'description', 'msatoshi', 'msatoshi_received'] }] } + +}; + +export const CLN_TABLE_FIELDS_DEF = { + payments: ['created_at', 'type', 'payment_hash', 'msatoshi_sent', 'msatoshi', 'amount_msat', 'amount_sent_msat', 'destination', 'status', 'memo'], + invoices: ['expires_at', 'paid_at', 'type', 'description', 'msatoshi', 'msatoshi_received', 'label', 'payment_hash', 'amount_msat', 'status', 'amount_received_msat'], + utxos: ['expires_at', 'paid_at', 'type', 'description', 'msatoshi', 'msatoshi_received', 'label', 'payment_hash', 'amount_msat', 'status', 'amount_received_msat'] +}; diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 15b85216..20a75f73 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -5,6 +5,7 @@ export const environment = { isDebugMode: false, AUTHENTICATE_API: API_URL + '/authenticate', CONF_API: API_URL + '/conf', + PAGE_SETTINGS_API: API_URL + '/pagesettings', BALANCE_API: '/balance', FEES_API: '/fees', PEERS_API: '/peers', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 40f0b155..32469639 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -5,6 +5,7 @@ export const environment = { isDebugMode: true, AUTHENTICATE_API: API_URL + '/authenticate', CONF_API: API_URL + '/conf', + PAGE_SETTINGS_API: API_URL + '/pagesettings', BALANCE_API: '/balance', FEES_API: '/fees', PEERS_API: '/peers',