Compare commits
6 Commits
minimal-pl
...
master
Author | SHA1 | Date | |
---|---|---|---|
d63b3f212e | |||
e028344b97 | |||
23414b6d8b | |||
002f6aa7fb | |||
f865c798a8 | |||
a199ab5750 |
21
Dockerfile
Normal file
21
Dockerfile
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
FROM alpine:3.15
|
||||||
|
|
||||||
|
RUN apk --update add redis
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
|
||||||
|
RUN python3 -m ensurepip
|
||||||
|
RUN pip3 install --no-cache --upgrade pip setuptools
|
||||||
|
|
||||||
|
ADD ./plugin-server.py /var/www/plugin-server/plugin-server.py
|
||||||
|
ADD ./plugin-loader.js /var/www/plugin-server/plugin-loader.js
|
||||||
|
ADD ./requirements.txt /var/www/plugin-server/requirements.txt
|
||||||
|
|
||||||
|
WORKDIR /var/www/plugin-server/
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
RUN ln -s /var/www/plugins/ ./
|
||||||
|
|
||||||
|
CMD /usr/bin/redis-server & python plugin-server.py
|
19
README.md
19
README.md
|
@ -8,13 +8,20 @@ It uses a javascript loader file, which loads plugins over the internet. This me
|
||||||
just serving static files, but we implemented our server code, which does one more trick. The plugin is loaded at the touch of "edit" button
|
just serving static files, but we implemented our server code, which does one more trick. The plugin is loaded at the touch of "edit" button
|
||||||
and that is it.
|
and that is it.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
Set the linked volume in `docker-compose.yml` to where you have your plugins directory. Then run the following command.
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.yml up -d
|
||||||
|
```
|
||||||
|
This will make the application available on `0.0.0.0:8085`.
|
||||||
|
|
||||||
## Plugin
|
## Plugin
|
||||||
|
|
||||||
Every plugin is a folder which is located where the server code is run from. A plugin consists of config.json file and any other file
|
Every plugin is a folder which is located where the server code is run from. A plugin consists of config.json file and any other file
|
||||||
you want. config.json instructs plugin loader which files to load. Currently we support three keys in config.json:
|
you want. config.json instructs plugin loader which files to load. Currently we support three keys in config.json:
|
||||||
|
|
||||||
1. javascript: list of javascript files, local files have to be written as: `"$LOCATION$/file.js"
|
1. javascript: list of javascript files, local files have to be written as: `"$LOCATION$/file.js"`
|
||||||
2. css: list of css stylesheets, local css have to be written as: `"$LOCATION$/file.js"
|
2. css: list of css stylesheets, local css have to be written as: `"$LOCATION$/file.js"`
|
||||||
3. global: key-value list of `{'name': file}` - content of a file is stored in global variable name in browser javacsript. Same rule as above applies to local files.
|
3. global: key-value list of `{'name': file}` - content of a file is stored in global variable name in browser javacsript. Same rule as above applies to local files.
|
||||||
|
|
||||||
Everytime you want to access local plugin files in javascript, css or wherever, you prepend `$LOCATION$/` to the url. The plugin server takes care, that correct url is generated. This settings is found in `URL` value in server python code.
|
Everytime you want to access local plugin files in javascript, css or wherever, you prepend `$LOCATION$/` to the url. The plugin server takes care, that correct url is generated. This settings is found in `URL` value in server python code.
|
||||||
|
@ -27,16 +34,16 @@ plugin_render(div, entry)
|
||||||
plugin_save(div)
|
plugin_save(div)
|
||||||
```
|
```
|
||||||
|
|
||||||
# Usage
|
## Lexonomy Entry Editor Settings
|
||||||
|
|
||||||
To use it, you must first paste the [javascript loader](plugin-loader.js) into custom entry editor section of Lexonomy configuration.
|
To use it, you must first paste the [javascript loader](plugin.js) into custom entry editor section of Lexonomy configuration.
|
||||||
Next, set correct url of your plugin. In order to work, the plugin must be accessible over HTTPS, if Lexonomy already runs on https.
|
Next, set correct url of your plugin. In order to work, the plugin must be accessible over HTTPS, if Lexonomy already runs on https.
|
||||||
In order to run a server, you need to run a default redis server and have these python packages installed: flask and redis.
|
In order to run a server, you need to run a default redis server and have these python packages installed: flask and redis.
|
||||||
Then you can run [flask server](plugin-server.py) on a server.
|
Then you can run [flask server](plugin-server.py) on a server.
|
||||||
|
|
||||||
## Example
|
### Example
|
||||||
|
|
||||||
Let's say you are hosting plugins on lexonomyplugins.example.com and that there is a plugin (a folder) called myplugin in `CWD` of the server python process. You need to set:
|
Let's say you are hosting plugins on lexonomyplugins.example.com and that there is a plugin (a folder) called myplugin in `CWD/plugins/` of the server python process. You need to set:
|
||||||
|
|
||||||
* `plugin: "//lexonomyplugins.example.com/myplugin"` in javascript loader,
|
* `plugin: "//lexonomyplugins.example.com/myplugin"` in javascript loader,
|
||||||
* `URL = "//lexonomyplugins.example.com/myplugin"` in python server,
|
* `URL = "//lexonomyplugins.example.com/myplugin"` in python server,
|
||||||
|
|
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
main:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./plugins:/var/www/plugins
|
||||||
|
ports:
|
||||||
|
- "8085:5000"
|
104
plugin-loader.js
104
plugin-loader.js
|
@ -1,48 +1,56 @@
|
||||||
function plugin_load(config, div, entry) {
|
function plugin_start(div, entry, editable) {
|
||||||
$("#load-pre-status").text("Loading...");
|
if(typeof plugin_init !== 'undefined') plugin_init(div, entry, editable);
|
||||||
var status_span = $('#load-status');
|
if(typeof plugin_render !== 'undefined') plugin_render(div, entry);
|
||||||
|
}
|
||||||
var len1 = config.javascript && config.javascript.length || 0;
|
|
||||||
var len2 = config.globals && config.javascript.length || 0;
|
function plugin_load(config, div, entry, editable) {
|
||||||
var num_progress = len1 + len2;
|
$("#load-pre-status").text("Loading...");
|
||||||
|
var status_span = $('#load-status');
|
||||||
status_span.after(" / " + num_progress);
|
|
||||||
status_span.text("0");
|
var len_deps = config.dependencies && config.dependencies.length || 0;
|
||||||
|
var len_globals = config.globals && Object.keys(config.globals).length || 0;
|
||||||
var progress = 0;
|
|
||||||
var check = function() {
|
var progress = 0;
|
||||||
if (++progress == num_progress) {
|
var num_progress = len_deps + len_globals;
|
||||||
status_span.parent().empty();
|
|
||||||
if(typeof plugin_init !== 'undefined') plugin_init(div, entry);
|
status_span.after(" / " + num_progress);
|
||||||
if(typeof plugin_render !== 'undefined') plugin_render(div, entry);
|
status_span.text("0");
|
||||||
}
|
|
||||||
}
|
var check = function() {
|
||||||
|
status_span.text(progress + 1 + "");
|
||||||
config.javascript.forEach(function(script) {
|
if (++progress >= num_progress) { // has to be >= for num_progress = 0
|
||||||
$.ajax({
|
status_span.parent().empty();
|
||||||
dataType: "script",
|
$.ajax({dataType: "script", cache: true, url: config.javascript, success: function() { plugin_start(div, entry, editable); }});
|
||||||
cache: true,
|
}
|
||||||
url: script,
|
}
|
||||||
success: function () {
|
|
||||||
status_span.text(progress + 1 + "");
|
config.dependencies.forEach(function(script) {
|
||||||
check();
|
$.ajax({
|
||||||
}
|
dataType: "script",
|
||||||
});
|
cache: true,
|
||||||
});
|
url: script,
|
||||||
|
success: check
|
||||||
Object.keys(config.globals).forEach(function(global_name) {
|
});
|
||||||
$.get(config.globals[global_name], function(data) {
|
});
|
||||||
window[global_name] = data;
|
|
||||||
check();
|
Object.keys(config.globals).forEach(function(global_name) {
|
||||||
});
|
$.get(config.globals[global_name], function(data) {
|
||||||
});
|
window[global_name] = data;
|
||||||
|
check();
|
||||||
config.css.forEach(function(css_url) {
|
});
|
||||||
var link = document.createElement('link');
|
});
|
||||||
link.rel = 'stylesheet';
|
|
||||||
link.type = 'text/css';
|
config.css.forEach(function(css_url) {
|
||||||
link.href = css_url;
|
var link = document.createElement('link');
|
||||||
link.media = 'all';
|
link.rel = 'stylesheet';
|
||||||
$('head').append(link);
|
link.type = 'text/css';
|
||||||
});
|
link.href = css_url;
|
||||||
}
|
link.media = 'all';
|
||||||
|
$('head').append(link);
|
||||||
|
});
|
||||||
|
|
||||||
|
// always adding a style element for to use for convenience
|
||||||
|
$('head').append($('<style id="dynamic_styler"></style>'));
|
||||||
|
|
||||||
|
if(num_progress == 0) check();
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ $LOCATION$ -> location of plugin
|
||||||
every plugin is its own folder and if folder is
|
every plugin is its own folder and if folder is
|
||||||
"myplugin" and this website's url is "http://example.com",
|
"myplugin" and this website's url is "http://example.com",
|
||||||
then $LOCATION$ gets the value of http://example.com/myplugin
|
then $LOCATION$ gets the value of http://example.com/myplugin
|
||||||
|
requirements: flask, redis
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
@ -22,8 +23,9 @@ from flask import Flask, Response, request
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
redis = redis.Redis(host='localhost', port=6379, db=0)
|
redis = redis.Redis(host='localhost', port=6379, db=0)
|
||||||
|
|
||||||
URL = "//plugins.lexonomy.cjvt.si"
|
URL = "https://plugins.lexonomy.cjvt.si"
|
||||||
REPLACE_STRING = "$LOCATION$"
|
REPLACE_STRING = "$LOCATION$"
|
||||||
|
PLUGINS_DIR = os.environ.get("PLUGINS_DIR_PATH", "plugins")
|
||||||
|
|
||||||
|
|
||||||
def check_cache(full_path):
|
def check_cache(full_path):
|
||||||
|
@ -48,6 +50,7 @@ def generate_etag(N=12):
|
||||||
|
|
||||||
def return_file(path, plugin=None):
|
def return_file(path, plugin=None):
|
||||||
full_path = path if plugin is None else"{}/{}".format(plugin, path)
|
full_path = path if plugin is None else"{}/{}".format(plugin, path)
|
||||||
|
full_path = f"{PLUGINS_DIR}/{full_path}"
|
||||||
|
|
||||||
if not os.path.isfile(full_path):
|
if not os.path.isfile(full_path):
|
||||||
return "File not found", 404
|
return "File not found", 404
|
||||||
|
@ -99,6 +102,7 @@ def return_file(path, plugin=None):
|
||||||
elif request.headers['If-None-Match'] != etag:
|
elif request.headers['If-None-Match'] != etag:
|
||||||
status_code = 200
|
status_code = 200
|
||||||
|
|
||||||
|
status_code = 200
|
||||||
return Response(result, mimetype=mt, headers=headers, status=status_code)
|
return Response(result, mimetype=mt, headers=headers, status=status_code)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
plugin: 'vsms',
|
plugin: 'vsms-py-v4.1',
|
||||||
url: '//plugins.lexonomy.cjvt.si',
|
url: '//plugins.lexonomy.cjvt.si',
|
||||||
editor: function(div, entry, uneditable) {
|
ske_gdex: ["Hungarian-bilingual-v1", 300],
|
||||||
|
editor: function(div, entry, uneditable) {
|
||||||
$(div).append('<p><span id="load-pre-status">INIT</span><span id="load-status"></span></p>');
|
$(div).append('<p><span id="load-pre-status">INIT</span><span id="load-status"></span></p>');
|
||||||
var progress_ctr = 2, config = null;
|
var progress_ctr = 2, config = null;
|
||||||
|
|
||||||
|
|
37
requirements.txt
Normal file
37
requirements.txt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with python 3.8
|
||||||
|
#
|
||||||
|
async-timeout==4.0.2
|
||||||
|
# via redis
|
||||||
|
click==8.1.3
|
||||||
|
# via flask
|
||||||
|
deprecated==1.2.13
|
||||||
|
# via redis
|
||||||
|
flask==2.1.2
|
||||||
|
# via
|
||||||
|
# -r requirements.in
|
||||||
|
# flask-cors
|
||||||
|
flask-cors==3.0.10
|
||||||
|
# via -r requirements.in
|
||||||
|
importlib-metadata==4.11.4
|
||||||
|
# via flask
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.2
|
||||||
|
# via flask
|
||||||
|
markupsafe==2.1.1
|
||||||
|
# via jinja2
|
||||||
|
packaging==21.3
|
||||||
|
# via redis
|
||||||
|
pyparsing==3.0.9
|
||||||
|
# via packaging
|
||||||
|
redis==4.3.3
|
||||||
|
# via -r requirements.in
|
||||||
|
six==1.16.0
|
||||||
|
# via flask-cors
|
||||||
|
werkzeug==2.1.2
|
||||||
|
# via flask
|
||||||
|
wrapt==1.14.1
|
||||||
|
# via deprecated
|
||||||
|
zipp==3.8.0
|
||||||
|
# via importlib-metadata
|
Loading…
Reference in New Issue
Block a user