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 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. const logger = getLogger("admin", 'debug')
  24. @RootComponent({
  25. injectable: IAdmin,
  26. injects: [
  27. GuildManager,
  28. ItemManager,
  29. RaidManager,
  30. CharacterManager,
  31. UserManager,
  32. Shoutbox,
  33. PubSub
  34. ]
  35. })
  36. export class FrontworkAdmin
  37. implements TableDefinitionExporter, IAdmin {
  38. knex:Knex
  39. config: RPCConfigLoader<AdminConf>
  40. rpcServer: RPCServer
  41. private express
  42. private httpServer
  43. constructor(private frontworkComponents: FrontworkComponent[] = []){
  44. this.config = new RPCConfigLoader<AdminConf>({
  45. name: "FrontworkAdminConf",
  46. getDefaultConfig: () => {
  47. return {
  48. httpPort: 8080,
  49. eventBusConf: {},
  50. dbConf: {
  51. client: 'sqlite3',
  52. connection: {
  53. filename: Path.resolve(__dirname, '../../../..', "data/frontworkAdmin.sqlite")
  54. },
  55. useNullAsDefault: true,
  56. }
  57. }
  58. }
  59. }, './config', this.configChangeHandler)
  60. }
  61. async start(){
  62. await this.makeKnex()
  63. this.startWebsocket()
  64. await Promise.all( this.frontworkComponents.map(c => c.initialize?c.initialize():undefined ))
  65. logger.debug(this.frontworkComponents.length+" components initialized")
  66. this.startWebserver()
  67. }
  68. stop(){
  69. Promise.race([
  70. Promise.all( this.frontworkComponents.map(c => c.stop?c.stop():undefined )),
  71. new Promise((res, rej) => { setTimeout(res, 250);})
  72. ]).catch(e => logger.warn(e))
  73. .finally(() => { process.exit(0) })
  74. }
  75. protected configChangeHandler = (conf:AdminConf, key?:string) => {
  76. if(key === 'dbConf'){
  77. this.makeKnex()
  78. }
  79. }
  80. getConfigKey(key:string){
  81. return this.config.getConfigKey(key)
  82. }
  83. setConfigKey(key:string, value:any){
  84. return this.config.setConfigKey(key, value)
  85. }
  86. getTableDefinitions(): TableDefiniton[]{
  87. return [
  88. ...this.frontworkComponents
  89. ]
  90. .filter(exp => exp.getTableDefinitions != null)
  91. .flatMap(exp => exp.getTableDefinitions())
  92. }
  93. private startWebsocket(){
  94. this.rpcServer = new RPCServer(20000, [
  95. ...this.frontworkComponents,
  96. {
  97. name: "debug",
  98. exportRPCs: () => [{
  99. name: 'dumpDb',
  100. call: async (table) => await this.knex(table).select('*')
  101. }]
  102. }
  103. ], {
  104. visibility: '0.0.0.0'
  105. })
  106. logger.debug("Websocket up on", 20000)
  107. }
  108. private startWebserver(){
  109. if(this.httpServer != null || this.express != null){
  110. logger.warn("Webserver is already running")
  111. return
  112. }
  113. let port:number = this.config.getConfig().httpPort
  114. this.express = express()
  115. this.express.use('/', express.static('static'))
  116. /**
  117. * get the compiled FrontendPlugins.js
  118. */
  119. this.express.get('/plugins/:id'+".js", async (request, response) => {
  120. const pth = Path.resolve("plugins/"+request.params.id, "FrontendPlugin.js");
  121. const file = await fs.readFile(pth)
  122. const frontend = file.toString()
  123. response.status(200)
  124. response.set('Content-Type', 'application/javascript')
  125. response.send(frontend)
  126. })
  127. /**
  128. * serve the index.html from the static folder
  129. */
  130. this.express.get("/", (request, response) => {
  131. response.status(200)
  132. response.sendFile('index.html');
  133. })
  134. /**
  135. * redirect all the other traffic to the single page app
  136. */
  137. this.express.get("*", (request, response) => {
  138. response.status(301)
  139. response.redirect('/')
  140. })
  141. this.httpServer = new http.Server(this.express)
  142. this.httpServer.listen(port, () => {
  143. logger.info('Admin panel listening for HTTP on *'+port)
  144. })
  145. }
  146. private stopWebserver(){
  147. if(this.httpServer == null || this.express == null){
  148. logger.warn("Webserver is not running")
  149. return
  150. }
  151. this.httpServer.close()
  152. this.httpServer = null
  153. this.express = null
  154. logger.info("Webserver stopped")
  155. }
  156. async makeKnex():Promise<Knex>{
  157. const conf:Knex.Config = this.config.getConfigKey("dbConf")
  158. logger.debug("Making new knex:", conf)
  159. if(conf.client === 'sqlite3'){
  160. mkdirSync(Path.dirname((<any>conf.connection).filename), {recursive: true})
  161. }
  162. this.knex = Knex(conf)
  163. if(conf.client === 'sqlite3'){
  164. await this.knex.raw('PRAGMA foreign_keys = ON');
  165. }
  166. await Promise.all(
  167. this.getTableDefinitions()
  168. //make unique by name
  169. .filter((other, index, self) => index === self.findIndex(
  170. (self) => self.name === other.name)
  171. )
  172. //create table if not exists
  173. .map(async (def)=> {
  174. const hasTable = await this.knex.schema.hasTable(def.name)
  175. if(!hasTable)
  176. return await this.knex.schema.createTable(def.name, def.tableBuilder)
  177. })
  178. )
  179. return this.knex
  180. }
  181. }
  182. process.on( 'SIGINT', function() {
  183. logger.info("Shutting down from SIGINT (Ctrl-C)" );
  184. Injector.resolve<FrontworkAdmin>(FrontworkAdmin).stop()
  185. })