diff --git a/src/backend/Admin.ts b/src/backend/Admin.ts index 7b50990..f1091e4 100644 --- a/src/backend/Admin.ts +++ b/src/backend/Admin.ts @@ -134,7 +134,7 @@ implements TableDefinitionExporter { logger.info("Webserver stopped") } - protected async makeKnex():Promise{ + public async makeKnex():Promise{ const conf:Knex.Config = this.config.getConfigKey("dbConf") logger.debug("Making new knex:", conf) diff --git a/src/backend/PluginLoader.ts b/src/backend/PluginLoader.ts index 36a4ad0..7f93227 100644 --- a/src/backend/PluginLoader.ts +++ b/src/backend/PluginLoader.ts @@ -1,19 +1,173 @@ import { RPCExporter } from "rpclibrary/js/src/Interfaces"; -import { Git, Types } from "upgiter" +import { Git } from "upgiter" +import { FolderStatus } from "upgiter/js/src/Types"; +import { Plugin } from "./Plugin"; +import { FrontworkAdmin } from "./Admin"; -export type GitUpdaterIfc = { - cloneRepo: (force:boolean) => Promise - getStatus: () => Promise - checkoutTag: (tag:string) => Promise - isOutdated: () => Promise +export type PluginLoaderIfc = { + PluginLoader: { + installPlugin: (name:string, force:boolean) => Promise + startPlugin: (name:string) => Promise + updatePlugin: (name:string) => Promise + setPluginVersion: (name:string, tag:string) => Promise + getLoadedPluginNames: () => Promise + selfUpdate: (force:boolean) => Promise + destroy: () => Promise + } } -class RPCUpgiter -extends Git.Updater -implements RPCExporter{ +class PluginLoader +implements RPCExporter{ + name = "PluginLoader" as "PluginLoader"; + private runningPlugins: Plugin[] + private pluginUpdaters:{[name in string]:Git.Updater} = {} + + constructor(private admin:FrontworkAdmin){} + + private async selfUpdate(force:boolean = false){ + const updater = new Git.Updater({ + schema: 'https', + localPath: './dist', + remoteHost: 'www.versioncontrol.me', + remotePath: 'frontwork-distribution', + repoName: 'admin' + }) + + 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(force) + + //this.destroy() + const installer = eval("require")("./Installer").installAdmin + installer(this.getPlugins()) + } + return status + } + + async installPlugin(name: string, force:boolean = false):Promise{ + //logger.warn("Cloning fb-dist/"+name+".git into ./plugins/"+name+" ..."+(force?" USING FORCE!":"")) + this.pluginUpdaters[name] = new Git.Updater({ + schema: 'https', + localPath: './dist', + remoteHost: 'www.versioncontrol.me', + remotePath: 'frontwork-distribution', + repoName: name + }) + //new GitUpdater("./plugins/"+name) + const status = await this.pluginUpdaters[name].cloneRepo(force) + return status + } + + async startPlugin(name:string):Promise{ + if(!this.pluginUpdaters[name]) return false + const status = await this.pluginUpdaters[name].getStatus() + if(!status.exists || status.empty || !status.tags || status.tags.length === 0){ + if(status.currentTag && !status.latestTag){ + //git glitches sometimes if you check immediately after clone + //logger.warn("re-fetching tag for "+name+"...") + return await this.startPlugin(name) + } + //logger.error("Bad repo status", name, status) + return false + } + +/* if(this.loadedPlugins[name]){ + logger.error("Plugin", name, "is already started") + return false + } +*/ + + let str = "../plugins/"+name+"/Plugin" + const pluginClass = await eval('require')(str) + const pluginObj = new pluginClass.default(this) + try{ + if(pluginObj.start) + await pluginObj.start() + //this.pushNotification({message: "Started plugin "+pluginObj.name, severity: "Important", topic:"admin"}) + this.addPlugin(pluginObj) + return true + }catch(e){ + //logger.error(e) + //this.pushNotification({message: "Start of plugin "+pluginObj.name+" failed because of "+String(e), severity: "Error", topic:"admin"}) + return false + } + } + + async updatePlugin(name:string):Promise{ + if(!this.pluginUpdaters[name]) return false + const status = await this.pluginUpdaters[name].getStatus() + if(!status.exists || status.empty || !status.tags || status.tags.length === 0){ + //logger.error("Bad repo status", name, status) + return false + } + if(status.currentTag == status.latestTag){ + //logger.warn(name, "already at latest tag") + return false + } + + /* + if(this.loadedPlugins[name]){ + logger.error("Plugin", name, "is running. Stop it first") + return false + } + */ + + this.pluginUpdaters[name].checkoutTag(status.latestTag!) + return true + } + + async setPluginVersion(pluginName:string, tag:string):Promise{ + const status = await this.pluginUpdaters[pluginName].getStatus() + if(!status.exists || !status.tags || status.tags.length === 0 || !status.tags.includes(tag)){ + //logger.error("Bad repo status", pluginName, status) + return status + } + + return await this.pluginUpdaters[pluginName].checkoutTag(tag) + } + + async addPlugin(plugin: Plugin){ + this.runningPlugins.push(plugin) + if(plugin.getTableDefinitions().length != 0) + await this.admin.makeKnex() + } + + removePlugin(plugin: Plugin){ + this.runningPlugins = this.runningPlugins.filter(p => p.name !== plugin.name) + } + + getPlugins():Plugin[]{ + return [...this.runningPlugins] + } + + private async destroy(){ + return + } exportRPCs(){ - return [] + return [{ + name: "installPlugin" as "installPlugin", + call: async (name:string, force = false) => {return await this.installPlugin(name, force)}, + },{ + name: "startPlugin" as "startPlugin", + call: async (name:string) => {return await this.startPlugin(name)}, + },{ + name: "updatePlugin" as "updatePlugin", + call: async (name:string) => {return await this.updatePlugin(name)}, + },{ + name: "setPluginVersion" as "setPluginVersion", + call: async (name:string, tag:string) => {return await this.setPluginVersion(name, tag)}, + },{ + name: "getLoadedPluginNames" as "getLoadedPluginNames", + call: async () => {return this.getPlugins().map(p => p.name)}, + },{ + name: "selfUpdate" as "selfUpdate", + call: async (force: boolean) => {return await this.selfUpdate(force)}, + },{ + name: "destroy" as "destroy", + call: async () => {return this.destroy()} + }] } } \ No newline at end of file