werks here

This commit is contained in:
peter
2019-08-03 20:28:36 +02:00
parent d9ccd4d9d3
commit 52ec9c8201
53 changed files with 561 additions and 349 deletions
+9 -4
View File
@@ -112,7 +112,7 @@ export class FrontblockAdmin extends ConfigLoader<AdminConf>{
//translate RPCs to socket-bound function metadata
const loadedPlugins = this.pm.getLoadedPlugins()
for(const name in loadedPlugins){
loadedPlugins[name].exportRPCs().forEach(rpc => {
loadedPlugins[name].backend.exportRPCs().forEach(rpc => {
const info = this.rpcToRpcInfo(name, rpc)
rpcInfos.push(info)
})
@@ -170,6 +170,13 @@ export class FrontblockAdmin extends ConfigLoader<AdminConf>{
this.express = express()
this.express.use(express.static('static'))
this.express.get('/plugins/:id'+".js", (request, response) => {
let frontend = this.pm.getFrontend(request.params.id)
response.status(200)
response.set('Content-Type', 'application/javascript')
response.send(frontend)
})
this.httpServer = new http.Server(this.express)
this.httpServer.listen(port, () => {
logger.info('Admin panel listening for HTTP on *'+port)
@@ -257,6 +264,4 @@ export class FrontblockAdmin extends ConfigLoader<AdminConf>{
}
}
(async() => {
new FrontblockAdmin()
})()
new FrontblockAdmin()
+7 -3
View File
@@ -22,7 +22,7 @@ export class PluginManagerPlugin extends PluginManager implements Plugin{
constructor(){
super()
this.loadedPlugins[this.name] = this
this.loadedPlugins[this.name] = {backend:this, frontend: this.loadFrontend(this.name)}
}
exportRPCs(): import("frontblock-generic/Plugin").socketioRPC[] {
@@ -44,8 +44,12 @@ export class PluginManagerPlugin extends PluginManager implements Plugin{
visibility: "private",
rpc: async () => { return this.getLoadedPlugins() },
type: 'call'
},
{
},{
name: "getLoadedPluginNames",
visibility: "private",
rpc: async () => { return this.getLoadedPluginNames() },
type: 'call'
},{
name: "installPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return await this.installPlugin(pluginName) },
+42 -18
View File
@@ -29,7 +29,7 @@ type PluginVersion = {
export type PluginVersioning = {
[pluginName in string]?: PluginVersion | string
}
export type PluginMap = {[pluginName:string]: Plugin}
export type PluginMap = {[pluginName:string]: {backend: Plugin, frontend:any}}
export type PluginManagerConfig = {
resourceLocation: string,
@@ -45,11 +45,13 @@ export default class PluginManager extends ConfigLoader<PluginManagerConfig&Plug
super("PluginManager")
this.cacheDir = path.join(this.conf.installDir, '.cache')
this.initialize()
this.loadPlugin("../../../../../../apiclient")
this.installPlugin("ApiClient")
}
getDefaultConfig(): PluginManagerConfig&PluginVersioning{
return {resourceLocation: "https://gitea.frontblock.me/api/v1/repos/fb-vendor/", installDir: "./plugins"}
return {resourceLocation: "https://gitea.frontblock.me/api/v1/repos/fb-vendor/", installDir: "plugins"}
}
@@ -91,6 +93,10 @@ export default class PluginManager extends ConfigLoader<PluginManagerConfig&Plug
}
}
public getLoadedPluginNames(): string[] {
return Object.keys(this.loadedPlugins)
}
public getLoadedPlugins(): PluginMap {
return this.loadedPlugins
}
@@ -98,12 +104,6 @@ export default class PluginManager extends ConfigLoader<PluginManagerConfig&Plug
private async downloadFile(url: URL, dest: string) {
try {
fs.access(path.dirname(dest), err => {
if (err && err.code === 'ENOENT') {
fs.mkdirSync(path.dirname(dest), {recursive: true})
}
})
const file = fs.createWriteStream(dest)
file.on('error', err => {throw new Error(err)})
const response = await fetch(url)
@@ -119,6 +119,7 @@ export default class PluginManager extends ConfigLoader<PluginManagerConfig&Plug
try {
logger.info("Downloading Plugin")
logger.error(this.conf.resourceLocation + pluginName + '/releases')
const response = await fetch(this.conf.resourceLocation + pluginName + '/releases')
if (!response.ok) throw new Error(response.statusText)
@@ -127,27 +128,44 @@ export default class PluginManager extends ConfigLoader<PluginManagerConfig&Plug
const latest = releases[0]
const version = latest.tag_name
const archiveURL = latest.assets.filter(asset => asset.name === pluginName + '.zip')[0].browser_download_url
const archiveURL = latest.assets.filter(asset => asset.name === 'plugin.zip')[0].browser_download_url
const checksumURL = latest.assets.filter(asset => asset.name === 'md5sum.txt')[0].browser_download_url
if (!version || ! archiveURL || !checksumURL) throw new Error('Malformed response')
await this.downloadFile(archiveURL, path.join(this.cacheDir, pluginName, version, 'plugin.zip'))
await this.downloadFile(checksumURL, path.join(this.cacheDir, pluginName, version, 'md5sum.txt'))
await fs.access(path.join(this.cacheDir, pluginName), err => {
if (err && err.code === 'ENOENT') {
fs.mkdirSync(path.join(this.cacheDir, pluginName), {recursive: true})
}
})
await this.downloadFile(archiveURL, path.join(this.cacheDir, pluginName, 'plugin.zip'))
await this.downloadFile(checksumURL, path.join(this.cacheDir, pluginName, 'md5sum.txt'))
} catch (error) {
logger.error(error)
}
}
private extractPlugin(pluginName: string){
fs
.createReadStream(path.join(this.cacheDir, pluginName, "plugin.zip"))
.pipe(unzip.Extract({ path: path.join(this.conf.installDir,pluginName) }));
}
public async installPlugin(pluginName: string) {
try {
await this.downloadPlugin(pluginName)
//TODO do something
await this.extractPlugin(pluginName)
await this.loadPlugin(pluginName)
} catch (error) {
logger.error(error)
}
}
public getFrontend(pluginName: string){
return this.loadedPlugins[pluginName].frontend.toString()
}
public async uninstallPlugin(pluginName: string) {
//TODO check for exists
logger.info("Deleting plugin", pluginName)
@@ -156,12 +174,18 @@ export default class PluginManager extends ConfigLoader<PluginManagerConfig&Plug
public async loadPlugin(pluginName:string) {
const pth = path.join(this.conf.installDir, pluginName, "Plugin")
logger.info("Loading plugin from fs", pth)
const clazz = await import(pth)
const pth = path.join("..", "..", this.conf.installDir, pluginName, "static")
logger.info("Loading plugin from fs", pth+"/Plugin")
const clazz = await import(pth+"/Plugin")
let obj:Plugin = new clazz.default()
await obj.start()
this.loadedPlugins[pluginName] = obj
this.loadedPlugins[pluginName] = {backend: obj, frontend: this.loadFrontend(pluginName)}
}
public loadFrontend(pluginName:string){
const pth = path.join("..", "..", this.conf.installDir, pluginName, "static")
logger.info("Loading frontend plugin from fs", path.join(__dirname,pth,"FrontendPlugin.js"))
return fs.readFileSync(path.join(__dirname, pth,"FrontendPlugin.js"))
}
@@ -171,7 +195,7 @@ export default class PluginManager extends ConfigLoader<PluginManagerConfig&Plug
return
}
logger.info("Unloading plugin ", pluginName)
this.loadedPlugins[pluginName].stop()
this.loadedPlugins[pluginName].backend.stop()
delete this.loadedPlugins[pluginName]
}
}
@@ -2650,9 +2650,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.3.211",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.211.tgz",
"integrity": "sha512-GZAiK3oHrs0K+LwH+HD+bdjZ17v40oQQdXbbd3dgrwgbENvazrGpcuIADSAREWnxzo9gADB1evuizrbXsnoU2Q==",
"version": "1.3.212",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.212.tgz",
"integrity": "sha512-H8z5Smi1s1u1zGegEBfbxUAzrxyk1JoRHHHrlNGfhxv3sTb+p/Jz7JDvrR4196Q/Ip8r4+XwWcLvKrUjFKoJAg==",
"dev": true
},
"elliptic": {
@@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
declare const fb
@Injectable()
export class DashboardService {
constructor() { }
getWidgetConfigs(): Promise<string[]> {
return fb.PluginManager.getLoadedPluginNames()
}
}
@@ -27,7 +27,6 @@ SystemJS.config({ meta: { '*': { authorization: true } } });
import { AfterViewInit, Component, Compiler, Injector, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { DashboardService } from '../dashboard.service';
import { WidgetConfig } from '../widget-config.model';
@Component({
selector: 'app-dashboard',
@@ -42,29 +41,32 @@ export class DashboardComponent implements AfterViewInit {
private injector: Injector) { }
ngAfterViewInit() {
this.loadWidgets();
setTimeout( () => {
this.loadWidgets();
}, 250)
}
private async loadWidgets() {
const widgets = await this.dashboardService.getWidgetConfigs().toPromise();
const widgets = await this.dashboardService.getWidgetConfigs()
widgets.forEach((widget) => this.createWidget(widget));
}
private async createWidget(widget: WidgetConfig) {
private async createWidget(pluginName: string) {
// import external module bundle
console.log(`Importing module bundle: ${widget.moduleBundlePath}`);
const module = await SystemJS.import(widget.moduleBundlePath);
const module = await SystemJS.import("plugins/"+pluginName+".js");
console.log(module)
// compile module
const moduleFactory = await this.compiler.compileModuleAsync(module[widget.moduleName]);
const moduleFactory = await this.compiler.compileModuleAsync(module["PluginModule"]);
// resolve component factory
const moduleRef = moduleFactory.create(this.injector);
const componentProvider = moduleRef.injector.get(widget.name);
const componentProvider = moduleRef.injector.get("provider");
const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(componentProvider);
// compile component
console.log(`Creating widget: ${widget.name}`);
console.log(`Creating widget: ${pluginName}`);
this.content.createComponent(componentFactory);
}

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

-33
View File
@@ -1,33 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
-28
View File
@@ -1,28 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
@@ -1,17 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { WidgetConfig } from './widget-config.model';
@Injectable()
export class DashboardService {
constructor(private http: HttpClient) { }
private readonly url = 'widgets-repo/widgets.config.json';
getWidgetConfigs(): Observable<WidgetConfig[]> {
return this.http.get<WidgetConfig[]>(this.url);
}
}
-5
View File
@@ -1,5 +0,0 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}
+1
View File
@@ -0,0 +1 @@
export { PluginModule } from './widget/module';
@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'exclamations',
template: `
<span style="color: blue">!!!</span>
`
})
export class ANestedComponent implements OnInit {
constructor() { }
ngOnInit() { }
}
+24
View File
@@ -0,0 +1,24 @@
import { Component, OnInit } from '@angular/core';
declare const fb
@Component({
selector: 'APICLIENT', //!!!!
template: `
<div class="card">
<div class="card-block">
<div class="card-title">
APICLIENT ${Object.keys(fb.ApiClient).join("<br>")}
</div>
<div class="card-text">
Hello World <exclamations></exclamations>
</div>
</div>
</div>
`
})
export class PluginComponent implements OnInit {
constructor() { }
ngOnInit() { }
}
+18
View File
@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PluginComponent } from './component';
import { ANestedComponent } from './a-nested.component';
@NgModule({
imports: [CommonModule],
declarations: [
PluginComponent,
ANestedComponent
],
entryComponents: [PluginComponent],
providers: [{
provide: 'provider',
useValue: PluginComponent
}]
})
export class PluginModule { }