You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Admin.ts 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. 'use strict'
  2. import { getLogger } from 'frontblock-generic/Types';
  3. import { promises as fs, mkdirSync } from "fs"
  4. import { RPCServer } from 'rpclibrary'
  5. import * as Path from 'path'
  6. import * as Knex from 'knex';
  7. import * as http from 'http';
  8. import * as express from 'express';
  9. import { GuildManager } from '../Components/Guild/GuildManager';
  10. import { ItemManager } from '../Components/Item/ItemManager';
  11. import { RaidManager } from '../Components/Raid/RaidManager';
  12. import { CharacterManager } from '../Components/Character/CharacterManager';
  13. import { UserManager } from '../Components/User/UserManager';
  14. import { RootComponent } from '../Injector/ServiceDecorator';
  15. import { TableDefinitionExporter } from '../Types/Interfaces';
  16. import { AdminConf, TableDefiniton } from '../Types/Types';
  17. import { RPCConfigLoader } from '../Components/RPCConfigLoader';
  18. import { FrontworkComponent } from '../Types/FrontworkComponent';
  19. import { IAdmin } from './Interface';
  20. import { Injector } from '../Injector/Injector';
  21. import { Shoutbox } from '../Components/Shoutbox/Shoutbox';
  22. import { PubSub } from '../Components/PubSub/PubSub';
  23. //import { ngExpressEngine } from '../../frontend/node_modules/@nguniversal/express-engine';
  24. //import { APP_BASE_HREF } from '../../frontend/';
  25. //import { AppServerModule } from './app.server.module';
  26. const logger = getLogger("admin", 'debug')
  27. @RootComponent({
  28. injectable: IAdmin,
  29. injects: [
  30. GuildManager,
  31. ItemManager,
  32. RaidManager,
  33. CharacterManager,
  34. UserManager,
  35. Shoutbox,
  36. PubSub
  37. ]
  38. })
  39. export class FrontworkAdmin
  40. implements TableDefinitionExporter, IAdmin {
  41. knex:Knex
  42. config: RPCConfigLoader<AdminConf>
  43. rpcServer: RPCServer
  44. private express
  45. private httpServer
  46. constructor(private frontworkComponents: FrontworkComponent[] = []){
  47. this.config = new RPCConfigLoader<AdminConf>({
  48. name: "FrontworkAdminConf",
  49. getDefaultConfig: () => {
  50. return {
  51. httpPort: 8080,
  52. eventBusConf: {},
  53. dbConf: {
  54. client: 'sqlite3',
  55. connection: {
  56. filename: Path.resolve(__dirname, '../../../..', "data/frontworkAdmin.sqlite")
  57. },
  58. useNullAsDefault: true,
  59. }
  60. }
  61. }
  62. }, './config', this.configChangeHandler)
  63. }
  64. async start(){
  65. await this.makeKnex()
  66. this.startWebsocket()
  67. await Promise.all( this.frontworkComponents.map(c => c.initialize?c.initialize():undefined ))
  68. logger.debug(this.frontworkComponents.length+" components initialized")
  69. this.startWebserver()
  70. }
  71. stop(){
  72. Promise.all([
  73. ...this.frontworkComponents.map(c => c.stop?c.stop():undefined ),
  74. ])
  75. .catch(e => logger.warn(e))
  76. .finally(() => {
  77. this.rpcServer.destroy()
  78. process.exit(0)
  79. })
  80. }
  81. protected configChangeHandler = (conf:AdminConf, key?:string) => {
  82. if(key === 'dbConf'){
  83. this.makeKnex()
  84. }
  85. }
  86. getConfigKey(key:string){
  87. return this.config.getConfigKey(key)
  88. }
  89. setConfigKey(key:string, value:any){
  90. return this.config.setConfigKey(key, value)
  91. }
  92. getTableDefinitions(): TableDefiniton[]{
  93. return [
  94. ...this.frontworkComponents
  95. ]
  96. .filter(exp => exp.getTableDefinitions != null)
  97. .flatMap(exp => exp.getTableDefinitions())
  98. }
  99. private startWebsocket(){
  100. this.rpcServer = new RPCServer(20000, [
  101. ...this.frontworkComponents,
  102. {
  103. name: "debug",
  104. exportRPCs: () => [{
  105. name: 'dumpDb',
  106. call: async (table) => await this.knex(table).select('*')
  107. }]
  108. }
  109. ], {
  110. visibility: '0.0.0.0'
  111. })
  112. logger.debug("Websocket up on", 20000)
  113. }
  114. private startWebserver(){
  115. if(this.httpServer != null || this.express != null){
  116. logger.warn("Webserver is already running")
  117. return
  118. }
  119. let port:number = this.config.getConfig().httpPort
  120. this.express = express()
  121. /*
  122. this.express.engine('html', ngExpressEngine({
  123. bootstrap: AppServerModule,
  124. }));
  125. this.express.set('view engine', 'html');
  126. this.express.set('views', 'static');
  127. // Serve static files from /browser
  128. this.express.get('*.*', express.static('static', {
  129. maxAge: '1y'
  130. }));
  131. this.express.get("/", (request, response) => {
  132. response.status(200)
  133. response.sendFile('index.html');
  134. })
  135. // All regular routes use the Universal engine
  136. this.express.get('*', (req, res) => {
  137. res.status(301)
  138. res.redirect('/')
  139. //res.render('index', { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  140. });
  141. */
  142. /* */
  143. // get the compiled FrontendPlugins.js
  144. this.express.use('/', express.static('static'))
  145. this.express.get('/plugins/:id'+".js", async (request, response) => {
  146. const pth = Path.resolve("plugins/"+request.params.id, "FrontendPlugin.js");
  147. const file = await fs.readFile(pth)
  148. const frontend = file.toString()
  149. response.status(200)
  150. response.set('Content-Type', 'application/javascript')
  151. response.send(frontend)
  152. })
  153. // serve the index.html from the static folder
  154. this.express.get("/", (request, response) => {
  155. response.status(200)
  156. response.sendFile('index.html');
  157. })
  158. // redirect all the other traffic to the single page app
  159. this.express.get("*", (request, response) => {
  160. response.status(301)
  161. response.redirect('/')
  162. })
  163. this.httpServer = new http.Server(this.express)
  164. this.httpServer.listen(port, () => {
  165. logger.info('Admin panel listening for HTTP on *'+port)
  166. })
  167. /* */
  168. }
  169. private stopWebserver(){
  170. if(this.httpServer == null || this.express == null){
  171. logger.warn("Webserver is not running")
  172. return
  173. }
  174. this.httpServer.close()
  175. this.httpServer = null
  176. this.express = null
  177. logger.info("Webserver stopped")
  178. }
  179. async makeKnex():Promise<Knex>{
  180. const conf:Knex.Config = this.config.getConfigKey("dbConf")
  181. logger.debug("Making new knex:", conf)
  182. if(conf.client === 'sqlite3'){
  183. mkdirSync(Path.dirname((<any>conf.connection).filename), {recursive: true})
  184. }
  185. this.knex = Knex(conf)
  186. if(conf.client === 'sqlite3'){
  187. await this.knex.raw('PRAGMA foreign_keys = ON');
  188. }
  189. await Promise.all(
  190. this.getTableDefinitions()
  191. //make unique by name
  192. .filter((other, index, self) => index === self.findIndex(
  193. (self) => self.name === other.name)
  194. )
  195. //create table if not exists
  196. .map(async (def)=> {
  197. const hasTable = await this.knex.schema.hasTable(def.name)
  198. if(!hasTable)
  199. return await this.knex.schema.createTable(def.name, def.tableBuilder)
  200. })
  201. )
  202. return this.knex
  203. }
  204. }
  205. process.on( 'SIGINT', function() {
  206. logger.info("Shutting down from SIGINT (Ctrl-C)" );
  207. Injector.resolve<FrontworkAdmin>(FrontworkAdmin).stop()
  208. })