'use strict' import * as Logger from 'log4js' import * as Knex from 'knex' import { FrontblockApiClient, FrontblockApiConf } from 'frontblock'; import { AdminBase } from 'frontblock-generic/Admin'; import express = require('express'); import http = require('http'); import bsock = require('bsock'); import { promises as fs } from "fs" import * as path from "path" import { install } from './Installer'; import { FrontblockApi } from 'frontblock-generic/Api'; import { Plugin } from 'frontblock-generic/Plugin'; import { socketioRPC } from 'frontblock-generic/RPC'; import { GitUpdater } from './GitUpdater'; var exec = require('child-process-promise').exec; Logger.configure({ appenders: { "admin": { type: 'stdout' }, //app: { type: 'file', filename: 'application.log' } }, categories: { default: { appenders: [ 'admin' ], level: 'debug' } } }) const logger = Logger.getLogger("admin") export type AdminConf = { httpPort: number} export class FrontblockAdmin extends AdminBase{ private express private httpServer private io = bsock.createServer() private wsServer = http.createServer() constructor(runningPlugins: Plugin[] = []){ super(runningPlugins) this.initialize() } getDefaultConfig(): { apiConf: FrontblockApiConf; } & AdminConf & { dbConf:Knex.Config; } { return { httpPort: 8080, apiConf: { apiHost: "api.testnet.frontblock.me", apiKey: "", apiPort: 10001, tls: false }, dbConf: { client: 'sqlite3', connection: { filename: "./data/ApiClient.sqlite" }, useNullAsDefault: true } } } protected makeApiClient(conf: FrontblockApiConf): FrontblockApi { // @ts-ignore if(this.apiClient) this.apiClient.disconnect() this.apiClient = new FrontblockApiClient(conf) // @ts-ignore this.apiClient.connect() return this.apiClient } private initialize(){ this.startWebserver() this.startWebsocket() } private destroy(){ this.startWebsocket() this.stopWebserver() } exportRPCs():socketioRPC[]{ return [ ...super.exportRPCs(), { name: 'selfUpdate', type:'call', func: async (force: boolean) => {return this.selfUpdate(force)}, visibility: 'private' }, ] } private async selfUpdate(force:boolean = false){ const updater = new GitUpdater("./dist") let status = await updater.getStatus() if(force || !status.remote || !status.remote.includes("fb-dist/admin") || !status.exists || status.empty || !status.currentTag){ logger.warn("Cloning fb-dist/admin into ./dist ..."+(force?" USING FORCE!":"")) status = await updater.cloneRepo("https://gitea.frontblock.me/fb-dist/admin.git", force) // TODO install() } return status } private startWebserver(){ if(this.httpServer != null || this.express != null){ logger.warn("Webserver is already running") return } let port:number = this.getConfig().httpPort this.express = express() this.express.use('/', express.static('static')) /** * get the compiled FrontendPlugins.js */ this.express.get('/plugins/:id'+".js", async (request, response) => { const pth = path.resolve("plugins/"+request.params.id, "FrontendPlugin.js"); const file = await fs.readFile(pth) const frontend = file.toString() response.status(200) response.set('Content-Type', 'application/javascript') response.send(frontend) }) /** * serve the index.html from the static folder */ this.express.get("/", (request, response) => { response.status(200) response.sendFile('index.html'); }) /** * redirect all the other traffic to the single * page app to the main entry point from where * a webpacked and rolled up index.html is serverd * and angular takes over routing */ this.express.get("*", (request, response) => { response.status(301) response.redirect('/') }) this.httpServer = new http.Server(this.express) this.httpServer.listen(port, () => { logger.info('Admin panel listening for HTTP on *'+port) }) } private stopWebserver(){ if(this.httpServer == null || this.express == null){ logger.warn("Webserver is not running") return } this.httpServer.close() this.httpServer = null this.express = null logger.info("Webserver stopped") } private startWebsocket(){ try{ this.io.attach(this.wsServer) this.io.on('socket', (socket) => { logger.info("New Websocket connection on port", socket.port) const handleError = (e: any) => { logger.info("Websocket closing", String(e)) socket.close() } socket.on('error', handleError) this.initApis(socket) }) logger.info('Admin websocket listening on *20000') this.wsServer.listen(20000) }catch(e){ logger.error(String(e)) } } } process.on( 'SIGINT', function() { logger.info( "Gracefully shutting down from SIGINT (Ctrl-C)" ); // some other closing procedures go here process.exit( ); })