Compare commits
3 Commits
653c1c584c
...
348adefb7c
Author | SHA1 | Date |
---|---|---|
Ferdinand Thiessen | 348adefb7c | |
Ferdinand Thiessen | dca52b764c | |
Ferdinand Thiessen | f6c229d2ef |
|
@ -36,18 +36,21 @@ class CustomJSONEncoder(JSONEncoder):
|
||||||
|
|
||||||
|
|
||||||
def __load_plugins(app):
|
def __load_plugins(app):
|
||||||
logger.info("Search for plugins")
|
logger.debug("Search for plugins")
|
||||||
|
|
||||||
app.config["FG_PLUGINS"] = {}
|
app.config["FG_PLUGINS"] = {}
|
||||||
for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugins"):
|
for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugins"):
|
||||||
logger.debug("Found plugin: >{}<".format(entry_point.name))
|
logger.debug(f"Found plugin: >{entry_point.name}<")
|
||||||
plugin = None
|
|
||||||
if entry_point.name in config and config[entry_point.name].get("enabled", False):
|
if entry_point.name == config["FLASCHENGEIST"]["auth"] or (
|
||||||
|
entry_point.name in config and config[entry_point.name].get("enabled", False)
|
||||||
|
):
|
||||||
|
logger.debug(f"Load plugin {entry_point.name}")
|
||||||
try:
|
try:
|
||||||
logger.info(f"Load plugin {entry_point.name}")
|
|
||||||
plugin = entry_point.load()
|
plugin = entry_point.load()
|
||||||
if not hasattr(plugin, "name"):
|
if not hasattr(plugin, "name"):
|
||||||
setattr(plugin, "name", entry_point.name)
|
setattr(plugin, "name", entry_point.name)
|
||||||
plugin = plugin(config[entry_point.name])
|
plugin = plugin(config.get(entry_point.name, {}))
|
||||||
if hasattr(plugin, "blueprint") and plugin.blueprint is not None:
|
if hasattr(plugin, "blueprint") and plugin.blueprint is not None:
|
||||||
app.register_blueprint(plugin.blueprint)
|
app.register_blueprint(plugin.blueprint)
|
||||||
except:
|
except:
|
||||||
|
@ -55,17 +58,18 @@ def __load_plugins(app):
|
||||||
f"Plugin {entry_point.name} was enabled, but could not be loaded due to an error.",
|
f"Plugin {entry_point.name} was enabled, but could not be loaded due to an error.",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
del plugin
|
|
||||||
continue
|
continue
|
||||||
if isinstance(plugin, AuthPlugin):
|
if isinstance(plugin, AuthPlugin):
|
||||||
logger.debug(f"Found authentication plugin: {entry_point.name}")
|
if entry_point.name != config["FLASCHENGEIST"]["auth"]:
|
||||||
if entry_point.name == config["FLASCHENGEIST"]["auth"]:
|
logger.debug(f"Unload not configured AuthPlugin {entry_point.name}")
|
||||||
app.config["FG_AUTH_BACKEND"] = plugin
|
del plugin
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.info(f"Using authentication plugin: {entry_point.name}")
|
||||||
|
app.config["FG_AUTH_BACKEND"] = plugin
|
||||||
else:
|
else:
|
||||||
del plugin
|
logger.info(f"Using plugin: {entry_point.name}")
|
||||||
continue
|
app.config["FG_PLUGINS"][entry_point.name] = plugin
|
||||||
if plugin:
|
|
||||||
app.config["FG_PLUGINS"][entry_point.name] = plugin
|
|
||||||
if "FG_AUTH_BACKEND" not in app.config:
|
if "FG_AUTH_BACKEND" not in app.config:
|
||||||
logger.error("No authentication plugin configured or authentication plugin not found")
|
logger.error("No authentication plugin configured or authentication plugin not found")
|
||||||
raise RuntimeError("No authentication plugin configured or authentication plugin not found")
|
raise RuntimeError("No authentication plugin configured or authentication plugin not found")
|
||||||
|
|
|
@ -77,6 +77,7 @@ def configure_app(app, test_config=None):
|
||||||
"auth": {"enabled": True},
|
"auth": {"enabled": True},
|
||||||
"roles": {"enabled": True},
|
"roles": {"enabled": True},
|
||||||
"users": {"enabled": True},
|
"users": {"enabled": True},
|
||||||
|
"scheduler": {"enabled": True},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,11 @@ root = "/api"
|
||||||
# Set secret key
|
# Set secret key
|
||||||
secret_key = "V3ryS3cr3t"
|
secret_key = "V3ryS3cr3t"
|
||||||
# Domain used by frontend
|
# Domain used by frontend
|
||||||
#domain = "flaschengeist.local"
|
|
||||||
|
[scheduler]
|
||||||
|
# Possible values are: "passive_web" (default), "active_web" and "system"
|
||||||
|
# See documentation
|
||||||
|
# cron = "passive_web"
|
||||||
|
|
||||||
[LOGGING]
|
[LOGGING]
|
||||||
# You can override all settings from the logging.toml here
|
# You can override all settings from the logging.toml here
|
||||||
|
@ -44,12 +48,8 @@ allowed_mimetypes = [
|
||||||
"image/webp"
|
"image/webp"
|
||||||
]
|
]
|
||||||
|
|
||||||
[auth_plain]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[auth_ldap]
|
[auth_ldap]
|
||||||
# Full documentation https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/plugins_auth_ldap
|
# Full documentation https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/plugins_auth_ldap
|
||||||
enabled = false
|
|
||||||
# host = "localhost"
|
# host = "localhost"
|
||||||
# port = 389
|
# port = 389
|
||||||
# base_dn = "dc=example,dc=com"
|
# base_dn = "dc=example,dc=com"
|
||||||
|
|
|
@ -96,7 +96,10 @@ class Plugin:
|
||||||
.one_or_none()
|
.one_or_none()
|
||||||
)
|
)
|
||||||
if setting is not None:
|
if setting is not None:
|
||||||
setting.value = value
|
if value is None:
|
||||||
|
db.session.delete(setting)
|
||||||
|
else:
|
||||||
|
setting.value = value
|
||||||
else:
|
else:
|
||||||
db.session.add(_PluginSetting(plugin=self.name, name=name, value=value))
|
db.session.add(_PluginSetting(plugin=self.name, name=name, value=value))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import pkg_resources
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
from flaschengeist import logger
|
||||||
|
from flaschengeist.utils.HTTP import no_content
|
||||||
|
|
||||||
|
from . import Plugin
|
||||||
|
|
||||||
|
|
||||||
|
class __Task:
|
||||||
|
def __init__(self, function, **kwags):
|
||||||
|
self.function = function
|
||||||
|
self.interval = timedelta(**kwags)
|
||||||
|
|
||||||
|
|
||||||
|
_scheduled_tasks: dict[__Task] = dict()
|
||||||
|
|
||||||
|
|
||||||
|
def scheduled(id: str, replace=False, **kwargs):
|
||||||
|
"""
|
||||||
|
kwargs: days, hours, minutes
|
||||||
|
"""
|
||||||
|
|
||||||
|
def real_decorator(function):
|
||||||
|
if id not in _scheduled_tasks or replace:
|
||||||
|
logger.info(f"Registered task: {id}")
|
||||||
|
_scheduled_tasks[id] = __Task(function, **kwargs)
|
||||||
|
else:
|
||||||
|
logger.debug(f"Skipping already registered task: {id}")
|
||||||
|
return function
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
|
||||||
|
class SchedulerPlugin(Plugin):
|
||||||
|
id = "dev.flaschengeist.scheduler"
|
||||||
|
name = "scheduler"
|
||||||
|
blueprint = Blueprint(name, __name__)
|
||||||
|
|
||||||
|
def __init__(self, config=None):
|
||||||
|
"""Constructor called by create_app
|
||||||
|
Args:
|
||||||
|
config: Dict configuration containing the plugin section
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __view_func():
|
||||||
|
self.run_tasks()
|
||||||
|
return no_content()
|
||||||
|
|
||||||
|
def __passiv_func(v):
|
||||||
|
try:
|
||||||
|
self.run_tasks()
|
||||||
|
except:
|
||||||
|
logger.error("Error while executing scheduled tasks!", exc_info=True)
|
||||||
|
|
||||||
|
self.version = pkg_resources.get_distribution(self.__module__.split(".")[0]).version
|
||||||
|
cron = None if config is None else config.get("cron", "passive_web").lower()
|
||||||
|
|
||||||
|
if cron is None or cron == "passive_web":
|
||||||
|
self.blueprint.teardown_app_request(__passiv_func)
|
||||||
|
elif cron == "active_web":
|
||||||
|
self.blueprint.add_url_rule("/cron", view_func=__view_func)
|
||||||
|
|
||||||
|
def run_tasks(self):
|
||||||
|
changed = False
|
||||||
|
now = datetime.now()
|
||||||
|
status = self.get_setting("status", default=dict())
|
||||||
|
|
||||||
|
for id, task in _scheduled_tasks.items():
|
||||||
|
last_run = status.setdefault(id, now)
|
||||||
|
if last_run + task.interval <= now:
|
||||||
|
logger.debug(
|
||||||
|
f"Run task {id}, was scheduled for {last_run + task.interval}, next iteration: {now + task.interval}"
|
||||||
|
)
|
||||||
|
task.function()
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
# Remove not registered tasks
|
||||||
|
for id in status.keys():
|
||||||
|
if id not in _scheduled_tasks.keys():
|
||||||
|
del status[id]
|
||||||
|
self.set_setting("status", status)
|
|
@ -1,17 +0,0 @@
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
from flaschengeist.utils.HTTP import no_content
|
|
||||||
|
|
||||||
_scheduled = set()
|
|
||||||
|
|
||||||
|
|
||||||
def scheduled(func):
|
|
||||||
_scheduled.add(func)
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
@current_app.route("/cron")
|
|
||||||
def __run_scheduled():
|
|
||||||
for function in _scheduled:
|
|
||||||
function()
|
|
||||||
return no_content()
|
|
13
readme.md
13
readme.md
|
@ -39,6 +39,19 @@ Configuration is done within the a `flaschengeist.toml`file, you can copy the on
|
||||||
|
|
||||||
Uncomment and change at least all the database parameters!
|
Uncomment and change at least all the database parameters!
|
||||||
|
|
||||||
|
#### CRON
|
||||||
|
Some functionality used by some plugins rely on regular updates,
|
||||||
|
but as flaschengeists works as an WSGI app it can not controll when it gets called.
|
||||||
|
|
||||||
|
So you have to configure one of the following options to call flaschengeists CRON tasks:
|
||||||
|
|
||||||
|
1. Passive Web-CRON: Every time an users calls flaschengeist a task is scheduled (**NOT RECOMMENDED**)
|
||||||
|
- Pros: No external configuration needed
|
||||||
|
- Cons: Slower user experience, no guaranteed execution time of tasks
|
||||||
|
2. Active Web-CRON: You configure a webworker to call `<flaschengeist>/cron`
|
||||||
|
- Pros: Guaranteed execution interval, no impact on user experience (at least if you do not limit wsgi worker threads)
|
||||||
|
- Cons: Uses one of the webserver threads while executing
|
||||||
|
|
||||||
### Database installation
|
### Database installation
|
||||||
The user needs to have full permissions to the database.
|
The user needs to have full permissions to the database.
|
||||||
If not you need to create user and database manually do (or similar on Windows):
|
If not you need to create user and database manually do (or similar on Windows):
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -68,6 +68,7 @@ setup(
|
||||||
"balance = flaschengeist.plugins.balance:BalancePlugin",
|
"balance = flaschengeist.plugins.balance:BalancePlugin",
|
||||||
"mail = flaschengeist.plugins.message_mail:MailMessagePlugin",
|
"mail = flaschengeist.plugins.message_mail:MailMessagePlugin",
|
||||||
"pricelist = flaschengeist.plugins.pricelist:PriceListPlugin",
|
"pricelist = flaschengeist.plugins.pricelist:PriceListPlugin",
|
||||||
|
"scheduler = flaschengeist.plugins.scheduler:SchedulerPlugin",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
cmdclass={
|
cmdclass={
|
||||||
|
|
Loading…
Reference in New Issue