mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Refactor plugin handling and add support for pro plugins
This commit is contained in:
parent
86ac600c44
commit
a9e5900dc1
10 changed files with 78 additions and 52 deletions
|
|
@ -66,7 +66,7 @@ try:
|
|||
elif db_config.get("USE_UI", {"value": "no"})["value"] == "yes":
|
||||
data["use_ui"] = "yes"
|
||||
|
||||
data["external_plugins"] = [f"{plugin['id']}/{plugin['version']}" for plugin in db.get_plugins(external=True)]
|
||||
data["external_plugins"] = [f"{plugin['id']}/{plugin['version']}" for plugin in db.get_plugins(_type="external")]
|
||||
data["os"] = {
|
||||
"name": "Linux",
|
||||
"version": "Unknown",
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def install_plugin(plugin_dir, db) -> bool:
|
|||
if EXTERNAL_PLUGINS_DIR.joinpath(metadata["id"], "plugin.json").is_file():
|
||||
old_version = None
|
||||
|
||||
for plugin in db.get_plugins(external=True):
|
||||
for plugin in db.get_plugins(_type="external"):
|
||||
if plugin["id"] == metadata["id"]:
|
||||
old_version = plugin["version"]
|
||||
break
|
||||
|
|
@ -179,7 +179,7 @@ try:
|
|||
|
||||
plugin_file.update(
|
||||
{
|
||||
"external": True,
|
||||
"type": "external",
|
||||
"page": False,
|
||||
"method": "scheduler",
|
||||
"data": value,
|
||||
|
|
@ -195,7 +195,7 @@ try:
|
|||
|
||||
lock = Lock()
|
||||
|
||||
for plugin in db.get_plugins(external=True, with_data=True):
|
||||
for plugin in db.get_plugins(_type="external", with_data=True):
|
||||
if plugin["method"] != "scheduler" and plugin["id"] not in external_plugins_ids:
|
||||
external_plugins.append(plugin)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from os.path import basename, normpath, join
|
|||
from pathlib import Path
|
||||
from re import compile as re_compile
|
||||
from sys import _getframe, path as sys_path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
||||
from time import sleep
|
||||
from traceback import format_exc
|
||||
|
||||
|
|
@ -356,7 +356,9 @@ class Database:
|
|||
has_all_tables = False
|
||||
continue
|
||||
missing_columns = []
|
||||
extra_columns = []
|
||||
|
||||
# Check if any columns are missing
|
||||
db_columns = inspector.get_columns(table)
|
||||
self.__logger.debug(f'Checking table "{table}" for missing columns')
|
||||
for column in Base.metadata.tables[table].columns:
|
||||
|
|
@ -365,12 +367,24 @@ class Database:
|
|||
self.__logger.warning(f'Column "{column.name}" is missing in table "{table}"')
|
||||
missing_columns.append(column)
|
||||
|
||||
# Check if any columns are extra
|
||||
self.__logger.debug(f'Checking table "{table}" for extra columns')
|
||||
for db_column in db_columns:
|
||||
self.__logger.debug(f'Checking column "{db_column["name"]}" in table "{table}"')
|
||||
if not any(column.name == db_column["name"] for column in Base.metadata.tables[table].columns):
|
||||
self.__logger.warning(f'Column "{db_column["name"]}" is extra in table "{table}"')
|
||||
extra_columns.append(db_column)
|
||||
|
||||
try:
|
||||
with self.__db_session() as session:
|
||||
if missing_columns:
|
||||
for column in missing_columns:
|
||||
self.__logger.warning(f'Adding column "{column.name}" to table "{table}"')
|
||||
session.execute(text(f"ALTER TABLE {table} ADD COLUMN {column.name} {column.type}"))
|
||||
if extra_columns:
|
||||
for column in extra_columns:
|
||||
self.__logger.warning(f'Removing column "{column["name"]}" from table "{table}"')
|
||||
session.execute(text(f"ALTER TABLE {table} DROP COLUMN {column['name']}"))
|
||||
session.commit()
|
||||
except BaseException:
|
||||
return False, format_exc()
|
||||
|
|
@ -400,7 +414,6 @@ class Database:
|
|||
"description": "The general settings for the server",
|
||||
"version": "0.1",
|
||||
"stream": "partial",
|
||||
"external": False,
|
||||
}
|
||||
else:
|
||||
settings = plugin.pop("settings", {})
|
||||
|
|
@ -423,8 +436,8 @@ class Database:
|
|||
if plugin["stream"] != db_plugin.stream:
|
||||
updates[Plugins.stream] = plugin["stream"]
|
||||
|
||||
if plugin.get("external", False) != db_plugin.external:
|
||||
updates[Plugins.external] = plugin.get("external", False)
|
||||
if plugin.get("type", "core") != db_plugin.type:
|
||||
updates[Plugins.type] = plugin.get("type", "core")
|
||||
|
||||
if plugin.get("method", "manual") != db_plugin.method:
|
||||
updates[Plugins.method] = plugin.get("method", "manual")
|
||||
|
|
@ -446,7 +459,7 @@ class Database:
|
|||
description=plugin["description"],
|
||||
version=plugin["version"],
|
||||
stream=plugin["stream"],
|
||||
external=plugin.get("external", False),
|
||||
type=plugin.get("type", "core"),
|
||||
method=plugin.get("method"),
|
||||
data=plugin.get("data"),
|
||||
checksum=plugin.get("checksum"),
|
||||
|
|
@ -1112,7 +1125,7 @@ class Database:
|
|||
"""Update external plugins from the database"""
|
||||
to_put = []
|
||||
with self.__db_session() as session:
|
||||
db_plugins = session.query(Plugins).with_entities(Plugins.id).filter_by(external=True).all()
|
||||
db_plugins = session.query(Plugins).with_entities(Plugins.id).filter_by(type="external").all()
|
||||
|
||||
db_ids = []
|
||||
if delete_missing and db_plugins:
|
||||
|
|
@ -1128,7 +1141,7 @@ class Database:
|
|||
settings = plugin.pop("settings", {})
|
||||
jobs = plugin.pop("jobs", [])
|
||||
page = plugin.pop("page", False)
|
||||
plugin["external"] = True
|
||||
plugin["type"] = "external"
|
||||
db_plugin = (
|
||||
session.query(Plugins)
|
||||
.with_entities(
|
||||
|
|
@ -1139,14 +1152,14 @@ class Database:
|
|||
Plugins.method,
|
||||
Plugins.data,
|
||||
Plugins.checksum,
|
||||
Plugins.external,
|
||||
Plugins.type,
|
||||
)
|
||||
.filter_by(id=plugin["id"])
|
||||
.first()
|
||||
)
|
||||
|
||||
if db_plugin is not None:
|
||||
if db_plugin.external is False:
|
||||
if db_plugin.type != "external":
|
||||
self.__logger.warning(
|
||||
f"Plugin \"{plugin['id']}\" is not external, skipping update (updating a non-external plugin is forbidden for security reasons)",
|
||||
)
|
||||
|
|
@ -1364,7 +1377,7 @@ class Database:
|
|||
description=plugin["description"],
|
||||
version=plugin["version"],
|
||||
stream=plugin["stream"],
|
||||
external=True,
|
||||
type="external",
|
||||
method=plugin["method"],
|
||||
data=plugin.get("data"),
|
||||
checksum=plugin.get("checksum"),
|
||||
|
|
@ -1474,17 +1487,19 @@ class Database:
|
|||
|
||||
return ""
|
||||
|
||||
def get_plugins(self, *, external: bool = False, with_data: bool = False) -> List[Dict[str, Any]]:
|
||||
def get_plugins(self, *, _type: Literal["all", "external", "pro"] = "all", with_data: bool = False) -> List[Dict[str, Any]]:
|
||||
"""Get all plugins from the database."""
|
||||
plugins = []
|
||||
with self.__db_session() as session:
|
||||
entities = [Plugins.id, Plugins.stream, Plugins.name, Plugins.description, Plugins.version, Plugins.external, Plugins.method, Plugins.checksum]
|
||||
entities = [Plugins.id, Plugins.stream, Plugins.name, Plugins.description, Plugins.version, Plugins.type, Plugins.method, Plugins.checksum]
|
||||
if with_data:
|
||||
entities.append(Plugins.data)
|
||||
for plugin in session.query(Plugins).with_entities(*entities).all():
|
||||
if external and not plugin.external:
|
||||
continue
|
||||
|
||||
db_plugins = session.query(Plugins).with_entities(*entities)
|
||||
if _type != "all":
|
||||
db_plugins = db_plugins.filter_by(type=_type)
|
||||
|
||||
for plugin in db_plugins.all():
|
||||
page = session.query(Plugin_pages).with_entities(Plugin_pages.id).filter_by(plugin_id=plugin.id).first()
|
||||
data = {
|
||||
"id": plugin.id,
|
||||
|
|
@ -1492,7 +1507,7 @@ class Database:
|
|||
"name": plugin.name,
|
||||
"description": plugin.description,
|
||||
"version": plugin.version,
|
||||
"external": plugin.external,
|
||||
"type": plugin.type,
|
||||
"method": plugin.method,
|
||||
"page": page is not None,
|
||||
"settings": {},
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ INTEGRATIONS_ENUM = Enum(
|
|||
"Unknown",
|
||||
name="integrations_enum",
|
||||
)
|
||||
PLUGIN_TYPES_ENUM = Enum("core", "external", "pro", name="plugin_types_enum")
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
|
|
@ -50,7 +51,7 @@ class Plugins(Base):
|
|||
description = Column(String(256), nullable=False)
|
||||
version = Column(String(32), nullable=False)
|
||||
stream = Column(String(16), nullable=False)
|
||||
external = Column(Boolean, default=False, nullable=False)
|
||||
type = Column(PLUGIN_TYPES_ENUM, default="core", nullable=False)
|
||||
method = Column(METHODS_ENUM, default="manual", nullable=False)
|
||||
data = Column(LargeBinary(length=(2**32) - 1), nullable=True)
|
||||
checksum = Column(String(128), nullable=True)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class Configurator:
|
|||
def get_settings(self) -> Dict[str, Any]:
|
||||
return self.__settings
|
||||
|
||||
def get_plugins(self, _type: Union[Literal["core"], Literal["external"]]) -> List[Dict[str, Any]]:
|
||||
def get_plugins(self, _type: Literal["core", "external", "pro"]) -> List[Dict[str, Any]]:
|
||||
return self.__core_plugins if _type == "core" else self.__external_plugins
|
||||
|
||||
def get_plugins_settings(self, _type: Union[Literal["core"], Literal["external"]]) -> Dict[str, Any]:
|
||||
|
|
@ -135,7 +135,7 @@ class Configurator:
|
|||
|
||||
data.update(
|
||||
{
|
||||
"external": True,
|
||||
"type": "external",
|
||||
"page": "ui" in listdir(dirname(file)),
|
||||
"method": "manual",
|
||||
"data": value,
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ if __name__ == "__main__":
|
|||
|
||||
# Check if any external plugin has been added by the user
|
||||
external_plugins = []
|
||||
db_plugins = db.get_plugins(external=True)
|
||||
db_plugins = db.get_plugins(_type="external")
|
||||
plugins_dir = Path(sep, "etc", "bunkerweb", "plugins")
|
||||
for filename in glob(str(plugins_dir.joinpath("*", "plugin.json"))):
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
|
|
@ -398,7 +398,7 @@ if __name__ == "__main__":
|
|||
|
||||
if (scheduler_first_start and db_plugins) or changes:
|
||||
generate_external_plugins(
|
||||
db.get_plugins(external=True, with_data=True),
|
||||
db.get_plugins(_type="external", with_data=True),
|
||||
original_path=plugins_dir,
|
||||
)
|
||||
SCHEDULER.update_jobs()
|
||||
|
|
@ -651,7 +651,7 @@ if __name__ == "__main__":
|
|||
if PLUGINS_NEED_GENERATION:
|
||||
CHANGES.append("external_plugins")
|
||||
generate_external_plugins(
|
||||
db.get_plugins(external=True, with_data=True),
|
||||
db.get_plugins(_type="external", with_data=True),
|
||||
original_path=plugins_dir,
|
||||
)
|
||||
SCHEDULER.update_jobs()
|
||||
|
|
|
|||
|
|
@ -999,21 +999,18 @@ def plugins():
|
|||
variables = deepcopy(request.form.to_dict())
|
||||
del variables["csrf_token"]
|
||||
|
||||
if variables["external"] != "True":
|
||||
flash(f"Can't delete internal plugin {variables['name']}", "error")
|
||||
if variables["type"] in ("core", "pro"):
|
||||
flash(f"Can't delete {variables['type']} plugin {variables['name']}", "error")
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
|
||||
plugins = app.config["CONFIG"].get_plugins()
|
||||
for plugin in deepcopy(plugins):
|
||||
if plugin["external"] is False or plugin["id"] == variables["name"]:
|
||||
del plugins[plugins.index(plugin)]
|
||||
for x, plugin in enumerate(deepcopy(plugins)):
|
||||
if plugin["type"] in ("core", "pro") or plugin["id"] == variables["name"]:
|
||||
del plugins[x]
|
||||
|
||||
err = db.update_external_plugins(plugins)
|
||||
if err:
|
||||
flash(
|
||||
f"Couldn't update external plugins to database: {err}",
|
||||
"error",
|
||||
)
|
||||
flash(f"Couldn't update external plugins to database: {err}", "error")
|
||||
flash(f"Deleted plugin {variables['name']} successfully")
|
||||
else:
|
||||
if not tmp_ui_path.exists() or not listdir(str(tmp_ui_path)):
|
||||
|
|
@ -1130,7 +1127,7 @@ def plugins():
|
|||
new_plugins.append(
|
||||
plugin_file
|
||||
| {
|
||||
"external": True,
|
||||
"type": "external",
|
||||
"page": "ui" in listdir(str(temp_folder_path)),
|
||||
"method": "ui",
|
||||
"data": value,
|
||||
|
|
@ -1183,7 +1180,7 @@ def plugins():
|
|||
if errors >= files_count:
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
|
||||
plugins = app.config["CONFIG"].get_plugins(external=True, with_data=True)
|
||||
plugins = app.config["CONFIG"].get_plugins(_type="external", with_data=True)
|
||||
for plugin in deepcopy(plugins):
|
||||
if plugin["id"] in new_plugins_ids:
|
||||
flash(f"Plugin {plugin['id']} already exists", "error")
|
||||
|
|
@ -1191,10 +1188,7 @@ def plugins():
|
|||
|
||||
err = db.update_external_plugins(new_plugins, delete_missing=False)
|
||||
if err:
|
||||
flash(
|
||||
f"Couldn't update external plugins to database: {err}",
|
||||
"error",
|
||||
)
|
||||
flash(f"Couldn't update external plugins to database: {err}", "error")
|
||||
|
||||
if operation:
|
||||
flash(operation)
|
||||
|
|
@ -1217,10 +1211,13 @@ def plugins():
|
|||
plugins = app.config["CONFIG"].get_plugins()
|
||||
plugins_internal = 0
|
||||
plugins_external = 0
|
||||
plugins_pro = 0
|
||||
|
||||
for plugin in plugins:
|
||||
if plugin["external"] is True:
|
||||
if plugin["type"] == "external":
|
||||
plugins_external += 1
|
||||
elif plugin["type"] == "pro":
|
||||
plugins_pro += 1
|
||||
else:
|
||||
plugins_internal += 1
|
||||
|
||||
|
|
@ -1229,6 +1226,7 @@ def plugins():
|
|||
plugins=plugins,
|
||||
plugins_internal=plugins_internal,
|
||||
plugins_external=plugins_external,
|
||||
plugins_pro=plugins_pro,
|
||||
username=current_user.get_id(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from json import loads as json_loads
|
|||
from pathlib import Path
|
||||
from re import search as re_search
|
||||
from subprocess import run, DEVNULL, STDOUT
|
||||
from typing import List, Tuple
|
||||
from typing import List, Literal, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
|
|
@ -83,8 +83,8 @@ class Config:
|
|||
**self.__settings,
|
||||
}
|
||||
|
||||
def get_plugins(self, *, external: bool = False, with_data: bool = False) -> List[dict]:
|
||||
plugins = self.__db.get_plugins(external=external, with_data=with_data)
|
||||
def get_plugins(self, *, _type: Literal["all", "external", "pro"] = "all", with_data: bool = False) -> List[dict]:
|
||||
plugins = self.__db.get_plugins(_type=_type, with_data=with_data)
|
||||
plugins.sort(key=itemgetter("name"))
|
||||
|
||||
general_plugin = None
|
||||
|
|
|
|||
12
src/ui/templates/plugins.html
vendored
12
src/ui/templates/plugins.html
vendored
|
|
@ -43,6 +43,18 @@ include "plugins_modal.html" %}
|
|||
{{plugins_external}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
PRO PLUGINS
|
||||
</p>
|
||||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
{{plugins_pro}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ try:
|
|||
"description": "The general settings for the server",
|
||||
"version": "0.1",
|
||||
"stream": "partial",
|
||||
"external": False,
|
||||
"type": "core",
|
||||
"checked": False,
|
||||
"page_checked": True,
|
||||
"settings": global_settings,
|
||||
|
|
@ -316,27 +316,27 @@ try:
|
|||
Plugins.description,
|
||||
Plugins.version,
|
||||
Plugins.stream,
|
||||
Plugins.external,
|
||||
Plugins.type,
|
||||
Plugins.method,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
for plugin in plugins:
|
||||
if not plugin.external and plugin.id in core_plugins:
|
||||
if plugin.type == "core" and plugin.id in core_plugins:
|
||||
current_plugin = core_plugins
|
||||
elif plugin.external and plugin.id in external_plugins:
|
||||
elif plugin.type == "external" and plugin.id in external_plugins:
|
||||
current_plugin = external_plugins
|
||||
else:
|
||||
print(
|
||||
f"❌ The {'external' if plugin.external else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but should not be, exiting ...",
|
||||
f"❌ The {'external' if plugin.type == 'external' else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but should not be, exiting ...: {plugin}",
|
||||
flush=True,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
if plugin.name != current_plugin[plugin.id]["name"] or plugin.description != current_plugin[plugin.id]["description"] or plugin.version != current_plugin[plugin.id]["version"] or plugin.stream != current_plugin[plugin.id]["stream"]:
|
||||
print(
|
||||
f"❌ The {'external' if plugin.external else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
f"❌ The {'external' if plugin.type == 'external' else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
+ f"{dumps({'name': plugin.name, 'description': plugin.description, 'version': plugin.version, 'stream': plugin.stream})}"
|
||||
+ f" (database) != {dumps({'name': current_plugin[plugin.id]['name'], 'description': current_plugin[plugin.id]['description'], 'version': current_plugin[plugin.id]['version'], 'stream': current_plugin[plugin.id]['stream']})} (file)", # noqa: E501
|
||||
flush=True,
|
||||
|
|
@ -357,7 +357,7 @@ try:
|
|||
or setting.multiple != current_plugin[plugin.id]["settings"][setting.id].get("multiple", None)
|
||||
):
|
||||
print(
|
||||
f"❌ The {'external' if plugin.external else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
f"❌ The {'external' if plugin.type == 'external' else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
+ f"{dumps({'default': setting.default, 'help': setting.help, 'label': setting.label, 'regex': setting.regex, 'type': setting.type})}"
|
||||
+ f" (database) != {dumps({'default': current_plugin[plugin.id]['settings'][setting.id]['default'], 'help': current_plugin[plugin.id]['settings'][setting.id]['help'], 'label': current_plugin[plugin.id]['settings'][setting.id]['label'], 'regex': current_plugin[plugin.id]['settings'][setting.id]['regex'], 'type': current_plugin[plugin.id]['settings'][setting.id]['type']})} (file)", # noqa: E501
|
||||
flush=True,
|
||||
|
|
|
|||
Loading…
Reference in a new issue