mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'pretzel' into 'dev'
Lots of Alpha 2.5 commits. See details See merge request crafty-controller/crafty-commander!46
This commit is contained in:
commit
85a118da81
71
DBCHANGES.md
Normal file
71
DBCHANGES.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Database change guide for contributors
|
||||
|
||||
When updating a database schema modify the schema in `app/classes/shared/models.py` and create a new migration with the `migration add <name>` command (in Crafty's prompt).
|
||||
|
||||
A full list of helper functions you can find in `app/classes/shared/models.py`
|
||||
|
||||
## Example migration files
|
||||
|
||||
### Rename column/field
|
||||
|
||||
```py
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.rename_column('my_table', 'old_name', 'new_name') # First argument can be model class OR table name
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.rename_column('my_table', 'new_name', 'old_name') # First argument can be model class OR table name
|
||||
|
||||
```
|
||||
|
||||
### Rename table/model
|
||||
|
||||
```py
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.rename_table('old_name', 'new_name') # First argument can be model class OR table name
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.rename_table('new_name', 'old_name') # First argument can be model class OR table name
|
||||
|
||||
```
|
||||
|
||||
### Create table/model
|
||||
|
||||
```py
|
||||
import peewee
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
class NewTable(peewee.Model):
|
||||
my_id = peewee.IntegerField(unique=True, primary_key=True)
|
||||
|
||||
class Meta:
|
||||
table_name = 'new_table'
|
||||
database = database
|
||||
create_table(NewTable)
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
drop_table('new_table') # Can be model class OR table name
|
||||
|
||||
```
|
||||
|
||||
### Add columns/fields
|
||||
|
||||
```py
|
||||
import peewee
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('table_name', new_field_name=peewee.CharField(default="")) # First argument can be model class OR table name
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('table_name', ['new_field_name']) # First argument can be model class OR table name
|
||||
|
||||
```
|
@ -22,9 +22,10 @@ except ModuleNotFoundError as e:
|
||||
|
||||
class MainPrompt(cmd.Cmd, object):
|
||||
|
||||
def __init__(self, tasks_manager):
|
||||
def __init__(self, tasks_manager, migration_manager):
|
||||
super().__init__()
|
||||
self.tasks_manager = tasks_manager
|
||||
self.migration_manager = migration_manager
|
||||
|
||||
# overrides the default Prompt
|
||||
prompt = "Crafty Controller v{} > ".format(helper.get_version_string())
|
||||
@ -47,6 +48,27 @@ class MainPrompt(cmd.Cmd, object):
|
||||
def do_exit(self, line):
|
||||
self.universal_exit()
|
||||
|
||||
def do_migrations(self, line):
|
||||
if (line == 'up'):
|
||||
self.migration_manager.up()
|
||||
elif (line == 'down'):
|
||||
self.migration_manager.down()
|
||||
elif (line == 'done'):
|
||||
console.info(self.migration_manager.done)
|
||||
elif (line == 'todo'):
|
||||
console.info(self.migration_manager.todo)
|
||||
elif (line == 'diff'):
|
||||
console.info(self.migration_manager.diff)
|
||||
elif (line == 'info'):
|
||||
console.info('Done: {}'.format(self.migration_manager.done))
|
||||
console.info('FS: {}'.format(self.migration_manager.todo))
|
||||
console.info('Todo: {}'.format(self.migration_manager.diff))
|
||||
elif (line.startswith('add ')):
|
||||
migration_name = line[len('add '):]
|
||||
self.migration_manager.create(migration_name, False)
|
||||
else:
|
||||
console.info('Unknown migration command')
|
||||
|
||||
def universal_exit(self):
|
||||
logger.info("Stopping all server daemons / threads")
|
||||
console.info("Stopping all server daemons / threads - This may take a few seconds")
|
||||
@ -62,3 +84,7 @@ class MainPrompt(cmd.Cmd, object):
|
||||
@staticmethod
|
||||
def help_exit():
|
||||
console.help("Stops the server if running, Exits the program")
|
||||
|
||||
@staticmethod
|
||||
def help_migrations():
|
||||
console.help("Only for advanced users. Use with caution")
|
||||
|
@ -4,6 +4,8 @@ import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
@ -110,10 +112,15 @@ class Controller:
|
||||
|
||||
@staticmethod
|
||||
def list_authorized_servers(userId):
|
||||
#servers = db_helper.get_authorized_servers(userId)
|
||||
servers = db_helper.get_authorized_servers_from_roles(userId)
|
||||
servers = db_helper.get_authorized_servers(userId)
|
||||
server_list = []
|
||||
for item in servers:
|
||||
server_list.append(item)
|
||||
role_servers = db_helper.get_authorized_servers_from_roles(userId)
|
||||
for item in role_servers:
|
||||
server_list.append(item)
|
||||
logger.debug("servers list = {}".format(servers))
|
||||
return servers
|
||||
return server_list
|
||||
|
||||
def get_server_data(self, server_id):
|
||||
for s in self.servers_list:
|
||||
@ -276,8 +283,32 @@ class Controller:
|
||||
if helper.check_file_perms(zip_path):
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(new_server_dir)
|
||||
zip_ref.extractall(tempDir)
|
||||
test = zip_ref.filelist[1].filename
|
||||
path_list = test.split('/')
|
||||
root_path = path_list[0]
|
||||
if len(path_list) > 1:
|
||||
for i in range(len(path_list)-2):
|
||||
root_path = os.path.join(root_path, path_list[i+1])
|
||||
|
||||
full_root_path = os.path.join(tempDir, root_path)
|
||||
|
||||
has_properties = False
|
||||
for item in os.listdir(full_root_path):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
try:
|
||||
shutil.move(os.path.join(full_root_path, item), os.path.join(new_server_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
|
||||
if not has_properties:
|
||||
logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port)))
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w") as f:
|
||||
f.write("server-port={}".format(port))
|
||||
f.close()
|
||||
zip_ref.close()
|
||||
else:
|
||||
return "false"
|
||||
|
||||
@ -313,7 +344,7 @@ class Controller:
|
||||
|
||||
return new_id
|
||||
|
||||
def remove_server(self, server_id):
|
||||
def remove_server(self, server_id, files):
|
||||
counter = 0
|
||||
for s in self.servers_list:
|
||||
|
||||
@ -330,7 +361,8 @@ class Controller:
|
||||
|
||||
if running:
|
||||
self.stop_server(server_id)
|
||||
|
||||
if files:
|
||||
shutil.rmtree(db_helper.get_server_data_by_id(server_id)['path'])
|
||||
# remove the server from the DB
|
||||
db_helper.remove_server(server_id)
|
||||
|
||||
|
@ -12,6 +12,7 @@ import logging
|
||||
import html
|
||||
import zipfile
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
@ -39,6 +40,7 @@ class Helpers:
|
||||
self.webroot = os.path.join(self.root_dir, 'app', 'frontend')
|
||||
self.servers_dir = os.path.join(self.root_dir, 'servers')
|
||||
self.backup_path = os.path.join(self.root_dir, 'backups')
|
||||
self.migration_dir = os.path.join(self.root_dir, 'app', 'migrations')
|
||||
|
||||
self.session_file = os.path.join(self.root_dir, 'app', 'config', 'session.lock')
|
||||
self.settings_file = os.path.join(self.root_dir, 'app', 'config', 'config.json')
|
||||
@ -564,6 +566,32 @@ class Helpers:
|
||||
zf.write(os.path.join(root, file),
|
||||
os.path.relpath(os.path.join(root, file),
|
||||
os.path.join(path, '..')))
|
||||
@staticmethod
|
||||
def copy_files(source, dest):
|
||||
if os.path.isfile(source):
|
||||
shutil.copyfile(source, dest)
|
||||
logger.info("Copying jar %s to %s", source, dest)
|
||||
else:
|
||||
logger.info("Source jar does not exist.")
|
||||
|
||||
@staticmethod
|
||||
def download_file(executable_url, jar_path):
|
||||
try:
|
||||
r = requests.get(executable_url, timeout=5)
|
||||
except Exception as ex:
|
||||
logger.error("Could not download executable: %s", ex)
|
||||
return False
|
||||
if r.status_code != 200:
|
||||
logger.error("Unable to download file from %s", executable_url)
|
||||
return False
|
||||
|
||||
try:
|
||||
open(jar_path, "wb").write(r.content)
|
||||
except Exception as e:
|
||||
logger.error("Unable to finish executable download. Error: %s", e)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@staticmethod
|
||||
def remove_prefix(text, prefix):
|
||||
|
532
app/classes/shared/migration.py
Normal file
532
app/classes/shared/migration.py
Normal file
@ -0,0 +1,532 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import typing as t
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from importlib import import_module
|
||||
from functools import wraps
|
||||
|
||||
try:
|
||||
from functools import cached_property
|
||||
except ImportError:
|
||||
from cached_property import cached_property
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import peewee
|
||||
from playhouse.migrate import (
|
||||
SchemaMigrator as ScM,
|
||||
SqliteMigrator as SqM,
|
||||
Operation, SQL, operation, SqliteDatabase,
|
||||
make_index_name, Context
|
||||
)
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(
|
||||
e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class MigrateHistory(peewee.Model):
|
||||
"""
|
||||
Presents the migration history in a database.
|
||||
"""
|
||||
|
||||
name = peewee.CharField(unique=True)
|
||||
migrated_at = peewee.DateTimeField(default=datetime.utcnow)
|
||||
|
||||
def __unicode__(self) -> str:
|
||||
"""
|
||||
String representation of this migration
|
||||
"""
|
||||
return self.name
|
||||
|
||||
|
||||
MIGRATE_TABLE = 'migratehistory'
|
||||
MIGRATE_TEMPLATE = '''# Generated by database migrator
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
{migrate}
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
||||
{rollback}'''
|
||||
VOID: t.Callable = lambda m, d: None
|
||||
|
||||
|
||||
def get_model(method):
|
||||
"""
|
||||
Convert string to model class.
|
||||
"""
|
||||
|
||||
@wraps(method)
|
||||
def wrapper(migrator, model, *args, **kwargs):
|
||||
if isinstance(model, str):
|
||||
return method(migrator, migrator.orm[model], *args, **kwargs)
|
||||
return method(migrator, model, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class Migrator(object):
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migrator
|
||||
"""
|
||||
if isinstance(database, peewee.Proxy):
|
||||
database = database.obj
|
||||
self.database: SqliteDatabase = database
|
||||
self.orm: t.Dict[str, peewee.Model] = {}
|
||||
self.operations: t.List[Operation] = []
|
||||
self.migrator = SqliteMigrator(database)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs operations.
|
||||
"""
|
||||
for op in self.operations:
|
||||
if isinstance(op, Operation):
|
||||
op.run()
|
||||
else:
|
||||
op()
|
||||
self.clean()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Cleans the operations.
|
||||
"""
|
||||
self.operations = list()
|
||||
|
||||
def sql(self, sql: str, *params):
|
||||
"""
|
||||
Executes raw SQL.
|
||||
"""
|
||||
self.operations.append(self.migrator.sql(sql, *params))
|
||||
|
||||
def create_table(self, model: peewee.Model) -> peewee.Model:
|
||||
"""
|
||||
Creates model and table in database.
|
||||
"""
|
||||
self.orm[model._meta.table_name] = model
|
||||
model._meta.database = self.database
|
||||
self.operations.append(model.create_table)
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_table(self, model: peewee.Model):
|
||||
"""
|
||||
Drops model and table from database.
|
||||
"""
|
||||
del self.orm[model._meta.table_name]
|
||||
self.operations.append(self.migrator.drop_table(model))
|
||||
|
||||
@get_model
|
||||
def add_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
|
||||
"""
|
||||
Creates new fields.
|
||||
"""
|
||||
for name, field in fields.items():
|
||||
model._meta.add_field(name, field)
|
||||
self.operations.append(self.migrator.add_column(
|
||||
model._meta.table_name, field.column_name, field))
|
||||
if field.unique:
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, (field.column_name,), unique=True))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def change_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
|
||||
"""
|
||||
Changes fields.
|
||||
"""
|
||||
for name, field in fields.items():
|
||||
old_field = model._meta.fields.get(name, field)
|
||||
old_column_name = old_field and old_field.column_name
|
||||
|
||||
model._meta.add_field(name, field)
|
||||
|
||||
if isinstance(old_field, peewee.ForeignKeyField):
|
||||
self.operations.append(self.migrator.drop_foreign_key_constraint(
|
||||
model._meta.table_name, old_column_name))
|
||||
|
||||
if old_column_name != field.column_name:
|
||||
self.operations.append(
|
||||
self.migrator.rename_column(
|
||||
model._meta.table_name, old_column_name, field.column_name))
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
on_delete = field.on_delete if field.on_delete else 'RESTRICT'
|
||||
on_update = field.on_update if field.on_update else 'RESTRICT'
|
||||
self.operations.append(self.migrator.add_foreign_key_constraint(
|
||||
model._meta.table_name, field.column_name,
|
||||
field.rel_model._meta.table_name, field.rel_field.name,
|
||||
on_delete, on_update))
|
||||
continue
|
||||
|
||||
self.operations.append(self.migrator.change_column(
|
||||
model._meta.table_name, field.column_name, field))
|
||||
|
||||
if field.unique == old_field.unique:
|
||||
continue
|
||||
|
||||
if field.unique:
|
||||
index = (field.column_name,), field.unique
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, *index))
|
||||
model._meta.indexes.append(index)
|
||||
else:
|
||||
index = (field.column_name,), old_field.unique
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, *index))
|
||||
model._meta.indexes.remove(index)
|
||||
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_columns(self, model: peewee.Model, names: str, **kwargs) -> peewee.Model:
|
||||
"""
|
||||
Removes fields from model.
|
||||
"""
|
||||
fields = [field for field in model._meta.fields.values()
|
||||
if field.name in names]
|
||||
cascade = kwargs.pop('cascade', True)
|
||||
for field in fields:
|
||||
self.__del_field__(model, field)
|
||||
if field.unique:
|
||||
index_name = make_index_name(
|
||||
model._meta.table_name, [field.column_name])
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
self.operations.append(
|
||||
self.migrator.drop_column(
|
||||
model._meta.table_name, field.column_name, cascade=False))
|
||||
return model
|
||||
|
||||
def __del_field__(self, model: peewee.Model, field: peewee.Field):
|
||||
"""
|
||||
Deletes field from model.
|
||||
"""
|
||||
model._meta.remove_field(field.name)
|
||||
delattr(model, field.name)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
obj_id_name = field.column_name
|
||||
if field.column_name == field.name:
|
||||
obj_id_name += '_id'
|
||||
delattr(model, obj_id_name)
|
||||
delattr(field.rel_model, field.backref)
|
||||
|
||||
@get_model
|
||||
def rename_column(self, model: peewee.Model, old_name: str, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames field in model.
|
||||
"""
|
||||
field = model._meta.fields[old_name]
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
old_name = field.column_name
|
||||
self.__del_field__(model, field)
|
||||
field.name = field.column_name = new_name
|
||||
model._meta.add_field(new_name, field)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
field.column_name = new_name = field.column_name + '_id'
|
||||
self.operations.append(self.migrator.rename_column(
|
||||
model._meta.table_name, old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def rename_table(self, model: peewee.Model, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames table in database.
|
||||
"""
|
||||
old_name = model._meta.table_name
|
||||
del self.orm[model._meta.table_name]
|
||||
model._meta.table_name = new_name
|
||||
self.orm[model._meta.table_name] = model
|
||||
self.operations.append(self.migrator.rename_table(old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_index(self, model: peewee.Model, *columns: str, **kwargs) -> peewee.Model:
|
||||
"""Create indexes."""
|
||||
unique = kwargs.pop('unique', False)
|
||||
model._meta.indexes.append((columns, unique))
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = unique
|
||||
field.index = not unique
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
|
||||
columns_.append(col)
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, columns_, unique=unique))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_index(self, model: peewee.Model, *columns: str) -> peewee.Model:
|
||||
"""Drop indexes."""
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
if not field:
|
||||
continue
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = field.index = False
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
columns_.append(col)
|
||||
index_name = make_index_name(model._meta.table_name, columns_)
|
||||
model._meta.indexes = [(cols, _) for (
|
||||
cols, _) in model._meta.indexes if columns != cols]
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Add not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = False
|
||||
self.operations.append(self.migrator.add_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Drop not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = True
|
||||
self.operations.append(self.migrator.drop_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_default(self, model: peewee.Model, name: str, default: t.Any) -> peewee.Model:
|
||||
"""Add default."""
|
||||
field = model._meta.fields[name]
|
||||
model._meta.defaults[field] = field.default = default
|
||||
self.operations.append(self.migrator.apply_default(
|
||||
model._meta.table_name, name, field))
|
||||
return model
|
||||
|
||||
|
||||
class SqliteMigrator(SqM):
|
||||
def drop_table(self, model):
|
||||
return lambda: model.drop_table(cascade=False)
|
||||
|
||||
@operation
|
||||
def change_column(self, table: str, column_name: str, field: peewee.Field):
|
||||
operations = [self.alter_change_column(table, column_name, field)]
|
||||
if not field.null:
|
||||
operations.extend([self.add_not_null(table, column_name)])
|
||||
return operations
|
||||
|
||||
def alter_change_column(self, table: str, column_name: str, field: peewee.Field) -> Operation:
|
||||
return self._update_column(table, column_name, lambda x, y: y)
|
||||
|
||||
@operation
|
||||
def sql(self, sql: str, *params) -> SQL:
|
||||
"""
|
||||
Executes raw SQL.
|
||||
"""
|
||||
return SQL(sql, *params)
|
||||
|
||||
def alter_add_column(
|
||||
self, table: str, column_name: str, field: peewee.Field, **kwargs) -> Operation:
|
||||
"""
|
||||
Fixes field name for ForeignKeys.
|
||||
"""
|
||||
name = field.name
|
||||
op = super().alter_add_column(
|
||||
table, column_name, field, **kwargs)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
field.name = name
|
||||
return op
|
||||
|
||||
|
||||
class MigrationManager(object):
|
||||
|
||||
filemask = re.compile(r"[\d]+_[^\.]+\.py$")
|
||||
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migration manager.
|
||||
"""
|
||||
if not isinstance(database, (peewee.Database, peewee.Proxy)):
|
||||
raise RuntimeError('Invalid database: {}'.format(database))
|
||||
self.database = database
|
||||
|
||||
@cached_property
|
||||
def model(self) -> peewee.Model:
|
||||
"""
|
||||
Initialize and cache the MigrationHistory model.
|
||||
"""
|
||||
MigrateHistory._meta.database = self.database
|
||||
MigrateHistory._meta.table_name = 'migratehistory'
|
||||
MigrateHistory._meta.schema = None
|
||||
MigrateHistory.create_table(True)
|
||||
return MigrateHistory
|
||||
|
||||
@property
|
||||
def done(self) -> t.List[str]:
|
||||
"""
|
||||
Scans migrations in the database.
|
||||
"""
|
||||
return [mm.name for mm in self.model.select().order_by(self.model.id)]
|
||||
|
||||
@property
|
||||
def todo(self):
|
||||
"""
|
||||
Scans migrations in the file system.
|
||||
"""
|
||||
if not os.path.exists(helper.migration_dir):
|
||||
logger.warning('Migration directory: {} does not exist.'.format(
|
||||
helper.migration_dir))
|
||||
os.makedirs(helper.migration_dir)
|
||||
return sorted(f[:-3] for f in os.listdir(helper.migration_dir) if self.filemask.match(f))
|
||||
|
||||
@property
|
||||
def diff(self) -> t.List[str]:
|
||||
"""
|
||||
Calculates difference between the filesystem and the database.
|
||||
"""
|
||||
done = set(self.done)
|
||||
return [name for name in self.todo if name not in done]
|
||||
|
||||
@cached_property
|
||||
def migrator(self) -> Migrator:
|
||||
"""
|
||||
Create migrator and setup it with fake migrations.
|
||||
"""
|
||||
migrator = Migrator(self.database)
|
||||
for name in self.done:
|
||||
self.up_one(name, migrator)
|
||||
return migrator
|
||||
|
||||
def compile(self, name, migrate='', rollback=''):
|
||||
"""
|
||||
Compiles a migration.
|
||||
"""
|
||||
name = datetime.utcnow().strftime('%Y%m%d%H%M%S') + '_' + name
|
||||
filename = name + '.py'
|
||||
path = os.path.join(helper.migration_dir, filename)
|
||||
with open(path, 'w') as f:
|
||||
f.write(MIGRATE_TEMPLATE.format(
|
||||
migrate=migrate, rollback=rollback, name=filename))
|
||||
|
||||
return name
|
||||
|
||||
def create(self, name: str = 'auto', auto: bool = False) -> t.Optional[str]:
|
||||
"""
|
||||
Creates a migration.
|
||||
"""
|
||||
migrate = rollback = ''
|
||||
if auto:
|
||||
raise NotImplementedError
|
||||
|
||||
logger.info('Creating migration "{}"'.format(name))
|
||||
name = self.compile(name, migrate, rollback)
|
||||
logger.info('Migration has been created as "{}"'.format(name))
|
||||
return name
|
||||
|
||||
def clear(self):
|
||||
"""Clear migrations."""
|
||||
self.model.delete().execute()
|
||||
|
||||
def up(self, name: t.Optional[str] = None):
|
||||
"""
|
||||
Runs all unapplied migrations.
|
||||
"""
|
||||
logger.info('Starting migrations')
|
||||
console.info('Starting migrations')
|
||||
|
||||
done = []
|
||||
diff = self.diff
|
||||
if not diff:
|
||||
logger.info('There is nothing to migrate')
|
||||
console.info('There is nothing to migrate')
|
||||
return done
|
||||
|
||||
migrator = self.migrator
|
||||
for mname in diff:
|
||||
done.append(self.up_one(mname, migrator))
|
||||
if name and name == mname:
|
||||
break
|
||||
|
||||
return done
|
||||
|
||||
def read(self, name: str):
|
||||
"""
|
||||
Reads a migration from a file.
|
||||
"""
|
||||
call_params = dict()
|
||||
if os.name == 'nt' and sys.version_info >= (3, 0):
|
||||
# if system is windows - force utf-8 encoding
|
||||
call_params['encoding'] = 'utf-8'
|
||||
with open(os.path.join(helper.migration_dir, name + '.py'), **call_params) as f:
|
||||
code = f.read()
|
||||
scope = {}
|
||||
code = compile(code, '<string>', 'exec', dont_inherit=True)
|
||||
exec(code, scope, None)
|
||||
return scope.get('migrate', VOID), scope.get('rollback', VOID)
|
||||
|
||||
def up_one(self, name: str, migrator: Migrator,
|
||||
rollback: bool = False) -> str:
|
||||
"""
|
||||
Runs a migration with a given name.
|
||||
"""
|
||||
try:
|
||||
migrate_fn, rollback_fn = self.read(name)
|
||||
with self.database.transaction():
|
||||
if rollback:
|
||||
logger.info('Rolling back "{}"'.format(name))
|
||||
rollback_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
self.model.delete().where(self.model.name == name).execute()
|
||||
else:
|
||||
logger.info('Migrate "{}"'.format(name))
|
||||
migrate_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
if name not in self.done:
|
||||
self.model.create(name=name)
|
||||
|
||||
logger.info('Done "{}"'.format(name))
|
||||
return name
|
||||
|
||||
except Exception:
|
||||
self.database.rollback()
|
||||
operation = 'Rollback' if rollback else 'Migration'
|
||||
logger.exception('{} failed: {}'.format(operation, name))
|
||||
raise
|
||||
|
||||
def down(self, name: t.Optional[str] = None):
|
||||
"""
|
||||
Rolls back migrations.
|
||||
"""
|
||||
if not self.done:
|
||||
raise RuntimeError('No migrations are found.')
|
||||
|
||||
name = self.done[-1]
|
||||
|
||||
migrator = self.migrator
|
||||
self.up_one(name, migrator, True)
|
||||
logger.warning('Rolled back migration: {}'.format(name))
|
@ -22,32 +22,12 @@ except ModuleNotFoundError as e:
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
schema_version = (0, 1, 0) # major, minor, patch semver
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
class BaseModel(Model):
|
||||
class Meta:
|
||||
database = database
|
||||
|
||||
class SchemaVersion(BaseModel):
|
||||
# DO NOT EVER CHANGE THE SCHEMA OF THIS TABLE
|
||||
# (unless we have a REALLY good reason to)
|
||||
# There will only ever be one row, and it allows the database loader to detect
|
||||
# what it needs to do on major version upgrades so you don't have to wipe the DB
|
||||
# every time you upgrade
|
||||
schema_major = IntegerField()
|
||||
schema_minor = IntegerField()
|
||||
schema_patch = IntegerField()
|
||||
|
||||
class Meta:
|
||||
table_name = 'schema_version'
|
||||
primary_key = CompositeKey('schema_major', 'schema_minor', 'schema_patch')
|
||||
|
||||
|
||||
class Users(BaseModel):
|
||||
class Users(Model):
|
||||
user_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
last_login = DateTimeField(default=datetime.datetime.now)
|
||||
@ -61,9 +41,10 @@ class Users(BaseModel):
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
database = database
|
||||
|
||||
|
||||
class Roles(BaseModel):
|
||||
class Roles(Model):
|
||||
role_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
last_update = DateTimeField(default=datetime.datetime.now)
|
||||
@ -71,18 +52,20 @@ class Roles(BaseModel):
|
||||
|
||||
class Meta:
|
||||
table_name = "roles"
|
||||
database = database
|
||||
|
||||
|
||||
class User_Roles(BaseModel):
|
||||
class User_Roles(Model):
|
||||
user_id = ForeignKeyField(Users, backref='user_role')
|
||||
role_id = ForeignKeyField(Roles, backref='user_role')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_roles'
|
||||
primary_key = CompositeKey('user_id', 'role_id')
|
||||
database = database
|
||||
|
||||
|
||||
class Audit_Log(BaseModel):
|
||||
class Audit_Log(Model):
|
||||
audit_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
user_name = CharField(default="")
|
||||
@ -91,8 +74,11 @@ class Audit_Log(BaseModel):
|
||||
server_id = IntegerField(default=None, index=True) # When auditing global events, use server ID 0
|
||||
log_msg = TextField(default='')
|
||||
|
||||
class Meta:
|
||||
database = database
|
||||
|
||||
class Host_Stats(BaseModel):
|
||||
|
||||
class Host_Stats(Model):
|
||||
time = DateTimeField(default=datetime.datetime.now, index=True)
|
||||
boot_time = CharField(default="")
|
||||
cpu_usage = FloatField(default=0)
|
||||
@ -106,9 +92,10 @@ class Host_Stats(BaseModel):
|
||||
|
||||
class Meta:
|
||||
table_name = "host_stats"
|
||||
database = database
|
||||
|
||||
|
||||
class Servers(BaseModel):
|
||||
class Servers(Model):
|
||||
server_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_uuid = CharField(default="", index=True)
|
||||
@ -122,23 +109,37 @@ class Servers(BaseModel):
|
||||
auto_start_delay = IntegerField(default=10)
|
||||
crash_detection = BooleanField(default=0)
|
||||
stop_command = CharField(default="stop")
|
||||
executable_update_url = CharField(default="")
|
||||
server_ip = CharField(default="127.0.0.1")
|
||||
server_port = IntegerField(default=25565)
|
||||
logs_delete_after = IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
table_name = "servers"
|
||||
database = database
|
||||
|
||||
class Role_Servers(BaseModel):
|
||||
|
||||
class User_Servers(Model):
|
||||
user_id = ForeignKeyField(Users, backref='user_server')
|
||||
server_id = ForeignKeyField(Servers, backref='user_server')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_servers'
|
||||
primary_key = CompositeKey('user_id', 'server_id')
|
||||
database = database
|
||||
|
||||
|
||||
class Role_Servers(Model):
|
||||
role_id = ForeignKeyField(Roles, backref='role_server')
|
||||
server_id = ForeignKeyField(Servers, backref='role_server')
|
||||
|
||||
class Meta:
|
||||
table_name = 'role_servers'
|
||||
primary_key = CompositeKey('role_id', 'server_id')
|
||||
database = database
|
||||
|
||||
|
||||
class Server_Stats(BaseModel):
|
||||
class Server_Stats(Model):
|
||||
stats_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_id = ForeignKeyField(Servers, backref='server', index=True)
|
||||
@ -156,13 +157,15 @@ class Server_Stats(BaseModel):
|
||||
players = CharField(default="")
|
||||
desc = CharField(default="Unable to Connect")
|
||||
version = CharField(default="")
|
||||
updating = BooleanField(default=False)
|
||||
|
||||
|
||||
class Meta:
|
||||
table_name = "server_stats"
|
||||
database = database
|
||||
|
||||
|
||||
class Commands(BaseModel):
|
||||
class Commands(Model):
|
||||
command_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_id = ForeignKeyField(Servers, backref='server', index=True)
|
||||
@ -173,9 +176,10 @@ class Commands(BaseModel):
|
||||
|
||||
class Meta:
|
||||
table_name = "commands"
|
||||
database = database
|
||||
|
||||
|
||||
class Webhooks(BaseModel):
|
||||
class Webhooks(Model):
|
||||
id = AutoField()
|
||||
name = CharField(max_length=64, unique=True, index=True)
|
||||
method = CharField(default="POST")
|
||||
@ -185,8 +189,10 @@ class Webhooks(BaseModel):
|
||||
|
||||
class Meta:
|
||||
table_name = "webhooks"
|
||||
database = database
|
||||
|
||||
class Schedules(BaseModel):
|
||||
|
||||
class Schedules(Model):
|
||||
schedule_id = IntegerField(unique=True, primary_key=True)
|
||||
server_id = ForeignKeyField(Servers, backref='schedule_server')
|
||||
enabled = BooleanField()
|
||||
@ -199,8 +205,10 @@ class Schedules(BaseModel):
|
||||
|
||||
class Meta:
|
||||
table_name = 'schedules'
|
||||
database = database
|
||||
|
||||
class Backups(BaseModel):
|
||||
|
||||
class Backups(Model):
|
||||
directories = CharField(null=True)
|
||||
max_backups = IntegerField()
|
||||
server_id = ForeignKeyField(Servers, backref='backups_server')
|
||||
@ -208,38 +216,15 @@ class Backups(BaseModel):
|
||||
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
database = database
|
||||
|
||||
|
||||
class db_builder:
|
||||
|
||||
@staticmethod
|
||||
def create_tables():
|
||||
with database:
|
||||
database.create_tables([
|
||||
Backups,
|
||||
Users,
|
||||
Roles,
|
||||
User_Roles,
|
||||
Host_Stats,
|
||||
Webhooks,
|
||||
Servers,
|
||||
Role_Servers,
|
||||
Server_Stats,
|
||||
Commands,
|
||||
Audit_Log,
|
||||
SchemaVersion,
|
||||
Schedules
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def default_settings():
|
||||
logger.info("Fresh Install Detected - Creating Default Settings")
|
||||
console.info("Fresh Install Detected - Creating Default Settings")
|
||||
SchemaVersion.insert({
|
||||
SchemaVersion.schema_major: schema_version[0],
|
||||
SchemaVersion.schema_minor: schema_version[1],
|
||||
SchemaVersion.schema_patch: schema_version[2]
|
||||
}).execute()
|
||||
default_data = helper.find_default_password()
|
||||
|
||||
username = default_data.get("username", 'admin')
|
||||
@ -266,39 +251,8 @@ class db_builder:
|
||||
return True
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def check_schema_version():
|
||||
svs = SchemaVersion.select().execute()
|
||||
if len(svs) != 1:
|
||||
raise exceptions.SchemaError("Multiple or no schema versions detected - potentially a failed upgrade?")
|
||||
sv = svs[0]
|
||||
svt = (sv.schema_major, sv.schema_minor, sv.schema_patch)
|
||||
logger.debug("Schema: found {}, expected {}".format(svt, schema_version))
|
||||
console.debug("Schema: found {}, expected {}".format(svt, schema_version))
|
||||
if sv.schema_major > schema_version[0]:
|
||||
raise exceptions.SchemaError("Major version mismatch - possible code reversion")
|
||||
elif sv.schema_major < schema_version[0]:
|
||||
db_shortcuts.upgrade_schema()
|
||||
|
||||
if sv.schema_minor > schema_version[1]:
|
||||
logger.warning("Schema minor mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
console.warning("Schema minor mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
elif sv.schema_minor < schema_version[1]:
|
||||
db_shortcuts.upgrade_schema()
|
||||
|
||||
if sv.schema_patch > schema_version[2]:
|
||||
logger.info("Schema patch mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
console.info("Schema patch mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
elif sv.schema_patch < schema_version[2]:
|
||||
db_shortcuts.upgrade_schema()
|
||||
logger.info("Schema validation successful! {}".format(schema_version))
|
||||
|
||||
class db_shortcuts:
|
||||
|
||||
@staticmethod
|
||||
def upgrade_schema():
|
||||
raise NotImplemented("I don't know who you are or how you reached this code, but this should NOT have happened. Please report it to the developer with due haste.")
|
||||
|
||||
@staticmethod
|
||||
def return_rows(query):
|
||||
rows = []
|
||||
@ -334,6 +288,7 @@ class db_shortcuts:
|
||||
def remove_server(server_id):
|
||||
with database.atomic():
|
||||
Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
|
||||
User_Servers.delete().where(User_Servers.server_id == server_id).execute()
|
||||
Servers.delete().where(Servers.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
@ -358,7 +313,37 @@ class db_shortcuts:
|
||||
server_data.append(db_helper.get_server_data_by_id(u.server_id))
|
||||
|
||||
return server_data
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_all_authorized_servers(user_id):
|
||||
user_servers = User_Servers.select().where(User_Servers.user_id == user_id)
|
||||
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
server_data = []
|
||||
roles_list = []
|
||||
role_server = []
|
||||
server_data = []
|
||||
|
||||
for u in user_servers:
|
||||
server_data.append(db_helper.get_server_data_by_id(u.server_id))
|
||||
|
||||
for u in user_roles:
|
||||
roles_list.append(db_helper.get_role(u.role_id))
|
||||
|
||||
for r in roles_list:
|
||||
role_test = Role_Servers.select().where(Role_Servers.role_id == r.get('role_id'))
|
||||
for t in role_test:
|
||||
role_server.append(t)
|
||||
|
||||
for s in role_server:
|
||||
found = False
|
||||
for item in user_servers:
|
||||
if s.server_id == item.server_id:
|
||||
found = True
|
||||
if not found:
|
||||
server_data.append(db_helper.get_server_data_by_id(s.server_id))
|
||||
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_from_roles(user_id):
|
||||
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
@ -394,16 +379,44 @@ class db_shortcuts:
|
||||
user_servers = User_Servers.select().where(User_Servers.user_id == user_id)
|
||||
authorized_servers = []
|
||||
server_data = []
|
||||
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
roles_list = []
|
||||
role_server = []
|
||||
|
||||
for u in user_servers:
|
||||
authorized_servers.append(db_helper.get_server_data_by_id(u.server_id))
|
||||
|
||||
for u in user_roles:
|
||||
roles_list.append(db_helper.get_role(u.role_id))
|
||||
|
||||
for r in roles_list:
|
||||
role_test = Role_Servers.select().where(Role_Servers.role_id == r.get('role_id'))
|
||||
for t in role_test:
|
||||
role_server.append(t)
|
||||
|
||||
for s in role_server:
|
||||
found = False
|
||||
for item in user_servers:
|
||||
if s.server_id == item.server_id:
|
||||
found = True
|
||||
if not found:
|
||||
authorized_servers.append(db_helper.get_server_data_by_id(s.server_id))
|
||||
|
||||
for s in authorized_servers:
|
||||
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)})
|
||||
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(
|
||||
Server_Stats.created.desc()).limit(1)
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0]})
|
||||
return server_data
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_user_roles_names(user_id):
|
||||
roles_list = []
|
||||
roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
for r in roles:
|
||||
roles_list.append(db_helper.get_role(r.role_id)['role_name'])
|
||||
return roles_list
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats_from_roles(user_id):
|
||||
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
@ -529,16 +542,34 @@ class db_shortcuts:
|
||||
roles = set()
|
||||
for r in roles_query:
|
||||
roles.add(r.role_id.role_id)
|
||||
#servers_query = User_Servers.select().join(Servers, JOIN.INNER).where(User_Servers.user_id == user_id)
|
||||
servers_query = User_Servers.select().join(Servers, JOIN.INNER).where(User_Servers.user_id == user_id)
|
||||
## TODO: this query needs to be narrower
|
||||
servers = set()
|
||||
#for s in servers_query:
|
||||
# servers.add(s.server_id.server_id)
|
||||
for s in servers_query:
|
||||
servers.add(s.server_id.server_id)
|
||||
user['roles'] = roles
|
||||
#user['servers'] = servers
|
||||
user['servers'] = servers
|
||||
#logger.debug("user: ({}) {}".format(user_id, user))
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def add_user_server(server_id, user_id):
|
||||
servers = User_Servers.insert({User_Servers.server_id: server_id, User_Servers.user_id: user_id}).execute()
|
||||
return servers
|
||||
|
||||
|
||||
@staticmethod
|
||||
def user_query(user_id):
|
||||
user_query = Users.select().where(Users.user_id == user_id)
|
||||
return user_query
|
||||
|
||||
@staticmethod
|
||||
def user_role_query(user_id):
|
||||
user_query = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
query = Roles.select().where(Roles.role_id == -1)
|
||||
for u in user_query:
|
||||
query = Roles.select().where(Roles.role_id == u.role_id)
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
def get_user(user_id):
|
||||
@ -555,7 +586,7 @@ class db_shortcuts:
|
||||
superuser: False,
|
||||
api_token: None,
|
||||
roles: [],
|
||||
servers: []
|
||||
servers: [],
|
||||
}
|
||||
user = model_to_dict(Users.get(Users.user_id == user_id))
|
||||
|
||||
@ -581,9 +612,9 @@ class db_shortcuts:
|
||||
elif key == "roles":
|
||||
added_roles = user_data['roles'].difference(base_data['roles'])
|
||||
removed_roles = base_data['roles'].difference(user_data['roles'])
|
||||
#elif key == "servers":
|
||||
# added_servers = user_data['servers'].difference(base_data['servers'])
|
||||
# removed_servers = base_data['servers'].difference(user_data['servers'])
|
||||
elif key == "servers":
|
||||
added_servers = user_data['servers'].difference(base_data['servers'])
|
||||
removed_servers = base_data['servers'].difference(user_data['servers'])
|
||||
elif key == "regen_api":
|
||||
if user_data['regen_api']:
|
||||
up_data['api_token'] = db_shortcuts.new_api_token()
|
||||
@ -600,10 +631,10 @@ class db_shortcuts:
|
||||
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
|
||||
User_Roles.delete().where(User_Roles.user_id == user_id).where(User_Roles.role_id.in_(removed_roles)).execute()
|
||||
|
||||
#for server in added_servers:
|
||||
# User_Servers.get_or_create(user_id=user_id, server_id=server)
|
||||
for server in added_servers:
|
||||
User_Servers.get_or_create(user_id=user_id, server_id=server)
|
||||
# # TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
|
||||
#User_Servers.delete().where(User_Servers.user_id == user_id).where(User_Servers.server_id.in_(removed_servers)).execute()
|
||||
User_Servers.delete().where(User_Servers.user_id == user_id).where(User_Servers.server_id.in_(removed_servers)).execute()
|
||||
if up_data:
|
||||
Users.update(up_data).where(Users.user_id == user_id).execute()
|
||||
|
||||
@ -842,6 +873,15 @@ class db_shortcuts:
|
||||
}
|
||||
return conf
|
||||
|
||||
@staticmethod
|
||||
def set_update(server_id, value):
|
||||
try:
|
||||
row = Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
except Exception as ex:
|
||||
logger.error("Database entry not found. ".format(ex))
|
||||
with database.atomic():
|
||||
Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
logger.debug("Updating server {} backup config with {}".format(server_id, locals()))
|
||||
|
@ -10,6 +10,7 @@ import threading
|
||||
import schedule
|
||||
import logging.config
|
||||
import zipfile
|
||||
from threading import Thread
|
||||
import html
|
||||
|
||||
|
||||
@ -86,11 +87,14 @@ class Server:
|
||||
self.settings = None
|
||||
self.updating = False
|
||||
self.server_id = None
|
||||
self.jar_update_url = None
|
||||
self.name = None
|
||||
self.is_crashed = False
|
||||
self.restart_count = 0
|
||||
self.crash_watcher_schedule = None
|
||||
self.stats = stats
|
||||
self.backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name="backup")
|
||||
self.is_backingup = False
|
||||
|
||||
def reload_server_settings(self):
|
||||
server_data = db_helper.get_server_data_by_id(self.server_id)
|
||||
@ -155,11 +159,16 @@ class Server:
|
||||
|
||||
def start_server(self):
|
||||
|
||||
logger.info("Start command detected. Reloading settings from DB for server {}".format(self.name))
|
||||
self.setup_server_run_command()
|
||||
# fail safe in case we try to start something already running
|
||||
if self.check_running():
|
||||
logger.error("Server is already running - Cancelling Startup")
|
||||
console.error("Server is already running - Cancelling Startup")
|
||||
return False
|
||||
if self.check_update():
|
||||
logger.error("Server is updating. Terminating startup.")
|
||||
return False
|
||||
|
||||
logger.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
console.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
@ -171,6 +180,19 @@ class Server:
|
||||
logger.info("Linux Detected")
|
||||
|
||||
logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command))
|
||||
<<<<<<< app/classes/shared/server.py
|
||||
try:
|
||||
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
|
||||
except Exception as ex:
|
||||
msg = "Server {} failed to start with error code: {}".format(self.name, ex)
|
||||
logger.error(msg)
|
||||
websocket_helper.broadcast('send_start_error', {
|
||||
'error': msg
|
||||
})
|
||||
return False
|
||||
websocket_helper.broadcast('send_start_reload', {
|
||||
})
|
||||
=======
|
||||
|
||||
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding='utf-8')
|
||||
out_buf = ServerOutBuf(self.process, self.server_id)
|
||||
@ -178,6 +200,7 @@ class Server:
|
||||
logger.debug('Starting virtual terminal listener for server {}'.format(self.name))
|
||||
threading.Thread(target=out_buf.check, daemon=True, name='{}_virtual_terminal'.format(self.server_id)).start()
|
||||
|
||||
>>>>>>> app/classes/shared/server.py
|
||||
self.is_crashed = False
|
||||
|
||||
self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
|
||||
@ -368,20 +391,50 @@ class Server:
|
||||
console.info("Removing old crash detection watcher thread")
|
||||
schedule.clear(self.name)
|
||||
|
||||
def is_backup_running(self):
|
||||
if self.is_backingup:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def backup_server(self):
|
||||
backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name="backup")
|
||||
logger.info("Starting Backup Thread for server {}.".format(self.settings['server_name']))
|
||||
#checks if the backup thread is currently alive for this server
|
||||
if not self.is_backingup:
|
||||
try:
|
||||
backup_thread.start()
|
||||
except Exception as ex:
|
||||
logger.error("Failed to start backup: {}".format(ex))
|
||||
return False
|
||||
else:
|
||||
logger.error("Backup is already being processed for server {}. Canceling backup request".format(self.settings['server_name']))
|
||||
return False
|
||||
logger.info("Backup Thread started for server {}.".format(self.settings['server_name']))
|
||||
|
||||
def a_backup_server(self):
|
||||
logger.info("Starting server {} (ID {}) backup".format(self.name, self.server_id))
|
||||
self.is_backingup = True
|
||||
conf = db_helper.get_backup_config(self.server_id)
|
||||
try:
|
||||
backup_filename = "{}/{}.zip".format(conf['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
backup_filename = "{}/{}.zip".format(self.settings['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
logger.info("Creating backup of server '{}' (ID#{}) at '{}'".format(self.settings['server_name'], self.server_id, backup_filename))
|
||||
helper.zip_directory(backup_filename, self.server_path)
|
||||
backup_list = self.list_backups()
|
||||
if len(self.list_backups()) > conf["max_backups"]:
|
||||
while len(self.list_backups()) > conf["max_backups"]:
|
||||
backup_list = self.list_backups()
|
||||
oldfile = backup_list[0]
|
||||
backup_path = self.settings['backup_path']
|
||||
old_file_name = oldfile['path']
|
||||
back_old_file = os.path.join(backup_path, old_file_name)
|
||||
logger.info("Removing old backup '{}'".format(oldfile))
|
||||
os.remove(oldfile)
|
||||
os.remove(back_old_file)
|
||||
self.is_backingup = False
|
||||
logger.info("Backup of server: {} completed".format(self.name))
|
||||
return
|
||||
except:
|
||||
logger.exception("Failed to create backup of server {} (ID {})".format(self.name, self.server_id))
|
||||
self.is_backingup = False
|
||||
return
|
||||
|
||||
def list_backups(self):
|
||||
conf = db_helper.get_backup_config(self.server_id)
|
||||
@ -390,3 +443,89 @@ class Server:
|
||||
return [{"path": os.path.relpath(f['path'], start=conf['backup_path']), "size": f["size"]} for f in files]
|
||||
else:
|
||||
return []
|
||||
|
||||
def jar_update(self):
|
||||
db_helper.set_update(self.server_id, True)
|
||||
update_thread = threading.Thread(target=self.a_jar_update, daemon=True, name="exe_update")
|
||||
update_thread.start()
|
||||
|
||||
def check_update(self):
|
||||
server_stats = db_helper.get_server_stats_by_id(self.server_id)
|
||||
if server_stats['updating']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def a_jar_update(self):
|
||||
error = False
|
||||
wasStarted = "-1"
|
||||
self.backup_server()
|
||||
#checks if server is running. Calls shutdown if it is running.
|
||||
if self.check_running():
|
||||
wasStarted = True
|
||||
logger.info("Server with PID {} is running. Sending shutdown command".format(self.PID))
|
||||
self.stop_threaded_server()
|
||||
else:
|
||||
wasStarted = False
|
||||
if len(websocket_helper.clients) > 0:
|
||||
# There are clients
|
||||
self.check_update()
|
||||
message = '<a data-id="'+str(self.server_id)+'" class=""> UPDATING...</i></a>'
|
||||
websocket_helper.broadcast('update_button_status', {
|
||||
'isUpdating': self.check_update(),
|
||||
'server_id': self.server_id,
|
||||
'wasRunning': wasStarted,
|
||||
'string': message
|
||||
})
|
||||
backup_dir = os.path.join(self.settings['path'], 'crafty_executable_backups')
|
||||
#checks if backup directory already exists
|
||||
if os.path.isdir(backup_dir):
|
||||
backup_executable = os.path.join(backup_dir, 'old_server.jar')
|
||||
else:
|
||||
logger.info("Executable backup directory not found for Server: {}. Creating one.".format(self.name))
|
||||
os.mkdir(backup_dir)
|
||||
backup_executable = os.path.join(backup_dir, 'old_server.jar')
|
||||
|
||||
if os.path.isfile(backup_executable):
|
||||
#removes old backup
|
||||
logger.info("Old backup found for server: {}. Removing...".format(self.name))
|
||||
os.remove(backup_executable)
|
||||
logger.info("Old backup removed for server: {}.".format(self.name))
|
||||
else:
|
||||
logger.info("No old backups found for server: {}".format(self.name))
|
||||
|
||||
current_executable = os.path.join(self.settings['path'], self.settings['executable'])
|
||||
|
||||
#copies to backup dir
|
||||
helper.copy_files(current_executable, backup_executable)
|
||||
|
||||
#boolean returns true for false for success
|
||||
downloaded = helper.download_file(self.settings['executable_update_url'], current_executable)
|
||||
|
||||
while db_helper.get_server_stats_by_id(self.server_id)['updating']:
|
||||
if downloaded and not self.is_backingup:
|
||||
logger.info("Executable updated successfully. Starting Server")
|
||||
|
||||
db_helper.set_update(self.server_id, False)
|
||||
if len(websocket_helper.clients) > 0:
|
||||
# There are clients
|
||||
self.check_update()
|
||||
websocket_helper.broadcast('notification', "Executable update finished for " + self.name)
|
||||
time.sleep(3)
|
||||
websocket_helper.broadcast('update_button_status', {
|
||||
'isUpdating': self.check_update(),
|
||||
'server_id': self.server_id,
|
||||
'wasRunning': wasStarted
|
||||
})
|
||||
websocket_helper.broadcast('notification', "Executable update finished for "+self.name)
|
||||
|
||||
db_helper.add_to_audit_log_raw('Alert', '-1', self.server_id, "Executable update finished for "+self.name, self.settings['server_ip'])
|
||||
if wasStarted:
|
||||
self.start_server()
|
||||
elif not downloaded and not self.is_backingup:
|
||||
time.sleep(5)
|
||||
db_helper.set_update(self.server_id, False)
|
||||
websocket_helper.broadcast('notification',
|
||||
"Executable update failed for " + self.name + ". Check log file for details.")
|
||||
logger.error("Executable download failed.")
|
||||
pass
|
||||
|
@ -5,6 +5,7 @@ import time
|
||||
import logging
|
||||
import threading
|
||||
import asyncio
|
||||
import shutil
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
@ -110,6 +111,18 @@ class TasksManager:
|
||||
elif command == "backup_server":
|
||||
svr.backup_server()
|
||||
|
||||
elif command == "update_executable":
|
||||
svr.jar_update()
|
||||
elif command == "delete_server":
|
||||
logger.info(
|
||||
"Removing server from panel for server: {}".format(c['server_id']['server_name']))
|
||||
self.controller.remove_server(c['server_id']['server_id'], False)
|
||||
|
||||
elif command == "delete_server_files":
|
||||
logger.info(
|
||||
"Removing server and all associated files for server: {}".format(c['server_id']['server_name']))
|
||||
self.controller.remove_server(c['server_id']['server_id'], True)
|
||||
|
||||
db_helper.mark_command_complete(c.get('command_id', None))
|
||||
|
||||
time.sleep(1)
|
||||
|
@ -213,7 +213,7 @@ class AjaxHandler(BaseHandler):
|
||||
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
console.warning("delete {} for server {}".format(file_path, server_id))
|
||||
console.warning("delete {} for server {}".format(dir_path, server_id))
|
||||
|
||||
if not self.check_server_id(server_id, 'del_dir'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
@ -7,6 +7,8 @@ import time
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from tornado import iostream
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Users, installer
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
@ -36,11 +38,11 @@ class PanelHandler(BaseHandler):
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
exec_user_role.add("Super User")
|
||||
else:
|
||||
defined_servers = self.controller.list_authorized_servers(exec_user_id)
|
||||
logger.debug(exec_user['roles'])
|
||||
for r in exec_user['roles']:
|
||||
role = db_helper.get_role(r)
|
||||
exec_user_role.add(role['role_name'])
|
||||
defined_servers = db_helper.get_all_authorized_servers(exec_user_id)
|
||||
|
||||
page_data = {
|
||||
# todo: make this actually pull and compare version data
|
||||
@ -109,10 +111,9 @@ class PanelHandler(BaseHandler):
|
||||
if exec_user['superuser'] == 1:
|
||||
page_data['servers'] = db_helper.get_all_servers_stats()
|
||||
else:
|
||||
#page_data['servers'] = db_helper.get_authorized_servers_stats(exec_user_id)
|
||||
ras = db_helper.get_authorized_servers_stats_from_roles(exec_user_id)
|
||||
logger.debug("ASFR: {}".format(ras))
|
||||
page_data['servers'] = ras
|
||||
user_auth = db_helper.get_authorized_servers_stats(exec_user_id)
|
||||
logger.debug("ASFR: {}".format(user_auth))
|
||||
page_data['servers'] = user_auth
|
||||
|
||||
for s in page_data['servers']:
|
||||
try:
|
||||
@ -137,10 +138,10 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
|
||||
if exec_user['superuser'] != 1:
|
||||
#if not db_helper.server_id_authorized(server_id, exec_user_id):
|
||||
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
if not db_helper.server_id_authorized(server_id, exec_user_id):
|
||||
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return False
|
||||
|
||||
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls']
|
||||
|
||||
@ -255,8 +256,43 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
|
||||
|
||||
elif page == 'panel_config':
|
||||
page_data['users'] = db_helper.get_all_users()
|
||||
page_data['roles'] = db_helper.get_all_roles()
|
||||
auth_servers = {}
|
||||
auth_role_servers = {}
|
||||
users_list = []
|
||||
role_users = {}
|
||||
roles = db_helper.get_all_roles()
|
||||
role_servers = []
|
||||
user_roles = {}
|
||||
for user in db_helper.get_all_users():
|
||||
user_roles_list = db_helper.get_user_roles_names(user.user_id)
|
||||
user_servers = db_helper.get_all_authorized_servers(user.user_id)
|
||||
servers = []
|
||||
for server in user_servers:
|
||||
servers.append(server['server_name'])
|
||||
new_item = {user.user_id: servers}
|
||||
auth_servers.update(new_item)
|
||||
data = {user.user_id: user_roles_list}
|
||||
user_roles.update(data)
|
||||
for role in roles:
|
||||
role_servers = []
|
||||
role = db_helper.get_role(role.role_id)
|
||||
for serv_id in role['servers']:
|
||||
role_servers.append(db_helper.get_server_data_by_id(serv_id)['server_name'])
|
||||
data = {role['role_id']: role_servers}
|
||||
auth_role_servers.update(data)
|
||||
|
||||
|
||||
page_data['auth-servers'] = auth_servers
|
||||
page_data['role-servers'] = auth_role_servers
|
||||
page_data['user-roles'] = user_roles
|
||||
|
||||
if exec_user['superuser'] == 1:
|
||||
page_data['users'] = db_helper.get_all_users()
|
||||
page_data['roles'] = db_helper.get_all_roles()
|
||||
else:
|
||||
page_data['users'] = db_helper.user_query(exec_user['user_id'])
|
||||
page_data['roles'] = db_helper.user_role_query(exec_user['user_id'])
|
||||
|
||||
for user in page_data['users']:
|
||||
if user.user_id != exec_user['user_id']:
|
||||
user.api_token = "********"
|
||||
@ -283,22 +319,39 @@ class PanelHandler(BaseHandler):
|
||||
page_data['roles_all'] = db_helper.get_all_roles()
|
||||
page_data['servers'] = []
|
||||
page_data['servers_all'] = self.controller.list_defined_servers()
|
||||
page_data['role-servers'] = []
|
||||
template = "panel/panel_edit_user.html"
|
||||
|
||||
elif page == "edit_user":
|
||||
page_data['new_user'] = False
|
||||
user_id = self.get_argument('id', None)
|
||||
user_servers = db_helper.get_authorized_servers(user_id)
|
||||
role_servers = db_helper.get_authorized_servers_from_roles(user_id)
|
||||
page_role_servers = []
|
||||
servers = set()
|
||||
for server in user_servers:
|
||||
flag = False
|
||||
for rserver in role_servers:
|
||||
if rserver['server_id'] == server['server_id']:
|
||||
flag = True
|
||||
if not flag:
|
||||
servers.add(server['server_id'])
|
||||
for server in role_servers:
|
||||
page_role_servers.append(server['server_id'])
|
||||
page_data['new_user'] = False
|
||||
page_data['user'] = db_helper.get_user(user_id)
|
||||
page_data['servers'] = db_helper.get_authorized_servers_stats_from_roles(user_id)
|
||||
page_data['servers'] = servers
|
||||
page_data['role-servers'] = page_role_servers
|
||||
page_data['roles_all'] = db_helper.get_all_roles()
|
||||
page_data['servers_all'] = self.controller.list_defined_servers()
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
return
|
||||
elif user_id is None:
|
||||
if user_id is None:
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
return
|
||||
elif not exec_user['superuser']:
|
||||
page_data['servers'] = []
|
||||
page_data['role-servers'] = []
|
||||
page_data['roles_all'] = []
|
||||
page_data['servers_all'] = []
|
||||
|
||||
if exec_user['user_id'] != page_data['user']['user_id']:
|
||||
page_data['user']['api_token'] = "********"
|
||||
@ -332,6 +385,12 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/panel_config")
|
||||
|
||||
elif page == "add_role":
|
||||
user_roles = {}
|
||||
for user in db_helper.get_all_users():
|
||||
user_roles_list = db_helper.get_user_roles_names(user.user_id)
|
||||
user_servers = db_helper.get_all_authorized_servers(user.user_id)
|
||||
data = {user.user_id: user_roles_list}
|
||||
user_roles.update(data)
|
||||
page_data['new_role'] = True
|
||||
page_data['role'] = {}
|
||||
page_data['role']['role_name'] = ""
|
||||
@ -339,6 +398,8 @@ class PanelHandler(BaseHandler):
|
||||
page_data['role']['created'] = "N/A"
|
||||
page_data['role']['last_update'] = "N/A"
|
||||
page_data['role']['servers'] = set()
|
||||
page_data['user-roles'] = user_roles
|
||||
page_data['users'] = db_helper.get_all_users()
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
@ -348,10 +409,20 @@ class PanelHandler(BaseHandler):
|
||||
template = "panel/panel_edit_role.html"
|
||||
|
||||
elif page == "edit_role":
|
||||
auth_servers = {}
|
||||
|
||||
user_roles = {}
|
||||
for user in db_helper.get_all_users():
|
||||
user_roles_list = db_helper.get_user_roles_names(user.user_id)
|
||||
user_servers = db_helper.get_all_authorized_servers(user.user_id)
|
||||
data = {user.user_id: user_roles_list}
|
||||
user_roles.update(data)
|
||||
page_data['new_role'] = False
|
||||
role_id = self.get_argument('id', None)
|
||||
page_data['role'] = db_helper.get_role(role_id)
|
||||
page_data['servers_all'] = self.controller.list_defined_servers()
|
||||
page_data['user-roles'] = user_roles
|
||||
page_data['users'] = db_helper.get_all_users()
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
@ -426,14 +497,17 @@ class PanelHandler(BaseHandler):
|
||||
auto_start_delay = self.get_argument('auto_start_delay', '10')
|
||||
server_ip = self.get_argument('server_ip', None)
|
||||
server_port = self.get_argument('server_port', None)
|
||||
executable_update_url = self.get_argument('executable_update_url', None)
|
||||
auto_start = int(float(self.get_argument('auto_start', '0')))
|
||||
crash_detection = int(float(self.get_argument('crash_detection', '0')))
|
||||
logs_delete_after = int(float(self.get_argument('logs_delete_after', '0')))
|
||||
subpage = self.get_argument('subpage', None)
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
return
|
||||
if not db_helper.server_id_authorized(server_id, exec_user_id):
|
||||
if not db_helper.server_id_authorized_from_roles(server_id, exec_user_id):
|
||||
self.redirect("/panel/error?error=Unauthorized access: invalid server id")
|
||||
return
|
||||
elif server_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
@ -454,6 +528,7 @@ class PanelHandler(BaseHandler):
|
||||
Servers.server_ip: server_ip,
|
||||
Servers.server_port: server_port,
|
||||
Servers.auto_start: auto_start,
|
||||
Servers.executable_update_url: executable_update_url,
|
||||
Servers.crash_detection: crash_detection,
|
||||
Servers.logs_delete_after: logs_delete_after,
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
@ -508,7 +583,18 @@ class PanelHandler(BaseHandler):
|
||||
regen_api = int(float(self.get_argument('regen_api', '0')))
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
user_data = {
|
||||
"username": username,
|
||||
"password": password0,
|
||||
}
|
||||
db_helper.update_user(user_id, user_data=user_data)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited user {} (UID:{}) password".format(username,
|
||||
user_id),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
self.redirect("/panel/panel_config")
|
||||
return
|
||||
elif username is None or username == "":
|
||||
self.redirect("/panel/error?error=Invalid username")
|
||||
@ -536,17 +622,28 @@ class PanelHandler(BaseHandler):
|
||||
if argument:
|
||||
roles.add(role.role_id)
|
||||
|
||||
servers = set()
|
||||
for server in self.controller.list_defined_servers():
|
||||
argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('server_{}_access'.format(server['server_id']), '0')
|
||||
)
|
||||
))
|
||||
if argument:
|
||||
servers.add(server['server_id'])
|
||||
|
||||
user_data = {
|
||||
"username": username,
|
||||
"password": password0,
|
||||
"enabled": enabled,
|
||||
"regen_api": regen_api,
|
||||
"roles": roles,
|
||||
"servers": servers,
|
||||
}
|
||||
db_helper.update_user(user_id, user_data=user_data)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited user {} (UID:{}) with roles {}".format(username, user_id, roles),
|
||||
"Edited user {} (UID:{}) with roles {} and servers {}".format(username, user_id, roles, servers),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
self.redirect("/panel/panel_config")
|
||||
@ -595,7 +692,11 @@ class PanelHandler(BaseHandler):
|
||||
servers.add(server['server_id'])
|
||||
|
||||
user_id = db_helper.add_user(username, password=password0, enabled=enabled)
|
||||
db_helper.update_user(user_id, {"roles":roles})
|
||||
user_data = {
|
||||
"roles": roles,
|
||||
"servers": servers,
|
||||
}
|
||||
db_helper.update_user(user_id, user_data)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
"Added user {} (UID:{})".format(username, user_id),
|
||||
|
@ -196,6 +196,7 @@ class ServerHandler(BaseHandler):
|
||||
server_type, server_version = server_parts
|
||||
# todo: add server type check here and call the correct server add functions if not a jar
|
||||
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
|
||||
db_helper.add_user_server(new_server_id, exec_user_id)
|
||||
db_helper.add_to_audit_log(exec_user_data['user_id'],
|
||||
"created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
|
||||
new_server_id,
|
||||
|
@ -220,6 +220,25 @@
|
||||
var webSocket;
|
||||
// {% end%}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_error', function (start_error) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if(x){
|
||||
x.remove()}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if(x){
|
||||
x.remove()}
|
||||
bootbox.alert(start_error.error);
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_reload', function (start_error) {
|
||||
location.reload()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function warn(message) {
|
||||
var closeEl = document.createElement('span');
|
||||
var strongEL = document.createElement('strong');
|
||||
|
@ -123,14 +123,15 @@
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="actions_serverlist">
|
||||
|
||||
<td id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
|
||||
{% if server['stats']['running'] %}
|
||||
<a class="stop_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-stop"></i></a>
|
||||
<a class="restart_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-sync"></i></a>
|
||||
{% elif server['stats']['updating']%}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class=""> UPDATING...</i></a>
|
||||
{% else %}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a>
|
||||
{% end %}
|
||||
|
||||
</td>
|
||||
@ -228,7 +229,11 @@ function send_command (server_id, command){
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){ location.reload(); }, 10000);
|
||||
setTimeout(function(){
|
||||
if (command != 'start_server'){
|
||||
location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
@ -281,6 +286,21 @@ $( document ).ready(function() {
|
||||
mem_percent.textContent = hostStats.mem_percent + '%';
|
||||
});
|
||||
}
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
var id = 'controls';
|
||||
var dataId = updateButton.server_id;
|
||||
var string = updateButton.string
|
||||
var id = id.concat(updateButton.server_id);
|
||||
if (updateButton.isUpdating){
|
||||
console.log(updateButton.isUpdating)
|
||||
document.getElementById(id).innerHTML = string;
|
||||
}
|
||||
else{
|
||||
window.location.reload()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$( ".clone_button" ).click(function() {
|
||||
server_id = $(this).attr("data-id");
|
||||
|
@ -45,6 +45,7 @@
|
||||
<th>Enabled</th>
|
||||
<th>API Token</th>
|
||||
<th>Allowed Servers</th>
|
||||
<th>Assigned Roles</th>
|
||||
<th>Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -66,7 +67,16 @@
|
||||
|
||||
</td>
|
||||
<td>{{ user.api_token }}</td>
|
||||
<td>{{ [] }}</td>
|
||||
<td class="server_list_{{user.user_id}}"><ul id="{{user.user_id}}">
|
||||
{% for item in data['auth-servers'][user.user_id] %}
|
||||
<li>{{item}}</li>
|
||||
{% end %}
|
||||
</ul></td>
|
||||
<td class="role_list_{{user.user_id}}"><ul>
|
||||
{% for item in data['user-roles'][user.user_id] %}
|
||||
<li>{{item}}</li>
|
||||
{% end %}
|
||||
|
||||
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
|
||||
</tr>
|
||||
{% end %}
|
||||
@ -95,6 +105,7 @@
|
||||
<tr class="rounded">
|
||||
<th>Role</th>
|
||||
<th>Allowed Servers</th>
|
||||
<th>Role Users</th>
|
||||
<th>Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -102,7 +113,21 @@
|
||||
{% for role in data['roles'] %}
|
||||
<tr>
|
||||
<td>{{ role.role_name }}</td>
|
||||
<td>{{ [] }}</td>
|
||||
<td class="role_list_{{role.role_id}}"><ul id="{{role.role_id}}">
|
||||
{% for item in data['role-servers'][role.role_id] %}
|
||||
<li>{{item}}</li>
|
||||
{% end %}
|
||||
</ul></td>
|
||||
<td><ul>
|
||||
{% for user in data['users'] %}
|
||||
{% for ruser in data['user-roles'][user.user_id] %}
|
||||
{% if ruser == role.role_name %}
|
||||
<li>{{ user.username }}</li>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% end %}
|
||||
</ul>
|
||||
</td>
|
||||
<td><a href="/panel/edit_role?id={{role.role_id}}"><i class="fas fa-pencil-alt"></i></a></td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
@ -40,15 +40,14 @@
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_name'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<i class="fas fa-cogs"></i>Config</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_name'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<i class="fas fa-folder-tree"></i>Other</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
{% if data['new_role'] %}
|
||||
@ -95,11 +94,24 @@
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2">Save</button>
|
||||
<button type="reset" class="btn btn-light">Cancel</button>
|
||||
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-12">
|
||||
<h6>Users Assigned to Role:</h6>
|
||||
<ul>
|
||||
{% for user in data['users'] %}
|
||||
{% for ruser in data['user-roles'][user.user_id] %}
|
||||
{% if ruser == data['role']['role_name'] %}
|
||||
<li>{{ user.username }}</li>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% end %}
|
||||
</ul>
|
||||
<br>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Role Config Area</h4>
|
||||
@ -114,7 +126,9 @@
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% if data['new_role'] %}
|
||||
<a class="btn btn-sm btn-danger disabled">Delete Role</a><br />
|
||||
<small>You cannot delete something that does not yet exist</small>
|
||||
@ -123,8 +137,6 @@
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -76,7 +76,7 @@
|
||||
<label for="password1">Repeat Password <small class="text-muted ml-1"></small> </label>
|
||||
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
|
||||
</div>
|
||||
|
||||
{% if len(data['servers_all']) > 0 %}
|
||||
<div class="form-group">
|
||||
<label for="role_membership">Roles <small class="text-muted ml-1"> - the roles this user is a member of</small> </label>
|
||||
<div class="table-responsive">
|
||||
@ -106,7 +106,6 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_membership">Servers <small class="text-muted ml-1"> - servers this user is allowed to access </small> </label>
|
||||
<div class="table-responsive">
|
||||
@ -123,9 +122,11 @@
|
||||
<td>{{ server['server_name'] }}</td>
|
||||
<td>
|
||||
{% if server['server_id'] in data['servers'] %}
|
||||
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" disabled>
|
||||
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="1">
|
||||
{% elif server['server_id'] in data['role-servers'] %}
|
||||
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="" disabled>
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" disabled>
|
||||
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" value="1">
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -136,6 +137,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-check-flat">
|
||||
<label for="enabled" class="form-check-label ml-4 mb-4">
|
||||
{% if data['user']['enabled'] %}
|
||||
@ -162,9 +164,9 @@
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
{% end %}
|
||||
<button type="submit" class="btn btn-success mr-2">Save</button>
|
||||
<button type="reset" class="btn btn-light">Cancel</button>
|
||||
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -106,13 +106,18 @@
|
||||
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL') }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverPort') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc') }}</small> </label>
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverIP') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverIP') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc') }}</small> </label>
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverPort') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc') }}</small> </label>
|
||||
<input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" >
|
||||
</div>
|
||||
|
||||
@ -159,10 +164,12 @@
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% if data['server_stats']['running'] %}
|
||||
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig', 'update') }}</button>
|
||||
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer') }}</a><br />
|
||||
<small>{{ translate('serverConfig', 'stopBeforeDeleting') }}</small>
|
||||
{% else %}
|
||||
<a href="/panel/remove_server?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer') }}</a>
|
||||
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig', 'update') }}</button>
|
||||
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer') }}</button>
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
@ -196,6 +203,105 @@
|
||||
|
||||
});
|
||||
|
||||
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
|
||||
|
||||
function send_command (server_id, command){
|
||||
<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){ location.reload(); }, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
if(command != "delete_server" && command != "delete_server_files"){
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingRequest") %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientUpdate") %} </div>'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteServer (){
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteFilesQuestion') %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage') %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverConfig', 'yesDeleteFiles') }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDeleteFiles') }}",
|
||||
className: 'btn-link',
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result){
|
||||
send_command(server_id, 'delete_server');
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete") %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDelete") %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
|
||||
return;}
|
||||
else{
|
||||
send_command(server_id, 'delete_server_files');
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete") %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDeleteFiles") %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
function deleteConfirm (){
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteServerQuestion') %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage') %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverConfig', 'yesDelete') }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDelete') }}",
|
||||
className: 'btn-link',
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result){
|
||||
return;
|
||||
return;}
|
||||
else{
|
||||
deleteServer();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -83,12 +83,19 @@
|
||||
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand') }}</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0">
|
||||
{% if data['server_stats']['updating']%}
|
||||
<div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'updating') }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart') %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop') }}</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="send_command(server_id, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start') }}</button>
|
||||
<button onclick="send_command(server_id, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart') %}</button>
|
||||
<button onclick="send_command(server_id, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop') }}</button>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
|
||||
</div>
|
||||
@ -120,12 +127,27 @@
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){ location.reload(); }, 10000);
|
||||
setTimeout(function(){
|
||||
if (command != 'start_server'){
|
||||
location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
if (updateButton.isUpdating){
|
||||
console.log(updateButton.isUpdating)
|
||||
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating") }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart") %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop") }}</button>';
|
||||
}
|
||||
else{
|
||||
window.location.reload()
|
||||
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start") }}</button><button onclick="send_command(server_id, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart") %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop") }}</button>';
|
||||
}
|
||||
});
|
||||
}
|
||||
// Convert running to lower case (example: 'True' converts to 'true') and
|
||||
// then to boolean via JSON.parse()
|
||||
let online = JSON.parse('{{ data['server_stats']['running'] }}'.toLowerCase());
|
||||
|
215
app/migrations/20210813111015_init.py
Normal file
215
app/migrations/20210813111015_init.py
Normal file
@ -0,0 +1,215 @@
|
||||
import peewee
|
||||
import datetime
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
db = database
|
||||
class Users(peewee.Model):
|
||||
user_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_login = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_update = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_ip = peewee.CharField(default="")
|
||||
username = peewee.CharField(default="", unique=True, index=True)
|
||||
password = peewee.CharField(default="")
|
||||
enabled = peewee.BooleanField(default=True)
|
||||
superuser = peewee.BooleanField(default=False)
|
||||
# we may need to revisit this
|
||||
api_token = peewee.CharField(default="", unique=True, index=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
database = db
|
||||
|
||||
class Roles(peewee.Model):
|
||||
role_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_update = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
role_name = peewee.CharField(default="", unique=True, index=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "roles"
|
||||
database = db
|
||||
|
||||
class User_Roles(peewee.Model):
|
||||
user_id = peewee.ForeignKeyField(Users, backref='user_role')
|
||||
role_id = peewee.ForeignKeyField(Roles, backref='user_role')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_roles'
|
||||
primary_key = peewee.CompositeKey('user_id', 'role_id')
|
||||
database = db
|
||||
|
||||
class Audit_Log(peewee.Model):
|
||||
audit_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
user_name = peewee.CharField(default="")
|
||||
user_id = peewee.IntegerField(default=0, index=True)
|
||||
source_ip = peewee.CharField(default='127.0.0.1')
|
||||
# When auditing global events, use server ID 0
|
||||
server_id = peewee.IntegerField(default=None, index=True)
|
||||
log_msg = peewee.TextField(default='')
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
class Host_Stats(peewee.Model):
|
||||
time = peewee.DateTimeField(default=datetime.datetime.now, index=True)
|
||||
boot_time = peewee.CharField(default="")
|
||||
cpu_usage = peewee.FloatField(default=0)
|
||||
cpu_cores = peewee.IntegerField(default=0)
|
||||
cpu_cur_freq = peewee.FloatField(default=0)
|
||||
cpu_max_freq = peewee.FloatField(default=0)
|
||||
mem_percent = peewee.FloatField(default=0)
|
||||
mem_usage = peewee.CharField(default="")
|
||||
mem_total = peewee.CharField(default="")
|
||||
disk_json = peewee.TextField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = "host_stats"
|
||||
database = db
|
||||
|
||||
class Servers(peewee.Model):
|
||||
server_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
server_uuid = peewee.CharField(default="", index=True)
|
||||
server_name = peewee.CharField(default="Server", index=True)
|
||||
path = peewee.CharField(default="")
|
||||
backup_path = peewee.CharField(default="")
|
||||
executable = peewee.CharField(default="")
|
||||
log_path = peewee.CharField(default="")
|
||||
execution_command = peewee.CharField(default="")
|
||||
auto_start = peewee.BooleanField(default=0)
|
||||
auto_start_delay = peewee.IntegerField(default=10)
|
||||
crash_detection = peewee.BooleanField(default=0)
|
||||
stop_command = peewee.CharField(default="stop")
|
||||
executable_update_url = peewee.CharField(default="")
|
||||
server_ip = peewee.CharField(default="127.0.0.1")
|
||||
server_port = peewee.IntegerField(default=25565)
|
||||
logs_delete_after = peewee.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
table_name = "servers"
|
||||
database = db
|
||||
|
||||
class User_Servers(peewee.Model):
|
||||
user_id = peewee.ForeignKeyField(Users, backref='user_server')
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='user_server')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_servers'
|
||||
primary_key = peewee.CompositeKey('user_id', 'server_id')
|
||||
database = db
|
||||
|
||||
class Role_Servers(peewee.Model):
|
||||
role_id = peewee.ForeignKeyField(Roles, backref='role_server')
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='role_server')
|
||||
|
||||
class Meta:
|
||||
table_name = 'role_servers'
|
||||
primary_key = peewee.CompositeKey('role_id', 'server_id')
|
||||
database = db
|
||||
|
||||
class Server_Stats(peewee.Model):
|
||||
stats_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='server', index=True)
|
||||
started = peewee.CharField(default="")
|
||||
running = peewee.BooleanField(default=False)
|
||||
cpu = peewee.FloatField(default=0)
|
||||
mem = peewee.FloatField(default=0)
|
||||
mem_percent = peewee.FloatField(default=0)
|
||||
world_name = peewee.CharField(default="")
|
||||
world_size = peewee.CharField(default="")
|
||||
server_port = peewee.IntegerField(default=25565)
|
||||
int_ping_results = peewee.CharField(default="")
|
||||
online = peewee.IntegerField(default=0)
|
||||
max = peewee.IntegerField(default=0)
|
||||
players = peewee.CharField(default="")
|
||||
desc = peewee.CharField(default="Unable to Connect")
|
||||
version = peewee.CharField(default="")
|
||||
updating = peewee.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "server_stats"
|
||||
database = db
|
||||
|
||||
class Commands(peewee.Model):
|
||||
command_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='server', index=True)
|
||||
user = peewee.ForeignKeyField(Users, backref='user', index=True)
|
||||
source_ip = peewee.CharField(default='127.0.0.1')
|
||||
command = peewee.CharField(default='')
|
||||
executed = peewee.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "commands"
|
||||
database = db
|
||||
|
||||
class Webhooks(peewee.Model):
|
||||
id = peewee.AutoField()
|
||||
name = peewee.CharField(max_length=64, unique=True, index=True)
|
||||
method = peewee.CharField(default="POST")
|
||||
url = peewee.CharField(unique=True)
|
||||
event = peewee.CharField(default="")
|
||||
send_data = peewee.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "webhooks"
|
||||
database = db
|
||||
|
||||
class Schedules(peewee.Model):
|
||||
schedule_id = peewee.IntegerField(unique=True, primary_key=True)
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='schedule_server')
|
||||
enabled = peewee.BooleanField()
|
||||
action = peewee.CharField()
|
||||
interval = peewee.IntegerField()
|
||||
interval_type = peewee.CharField()
|
||||
start_time = peewee.CharField(null=True)
|
||||
command = peewee.CharField(null=True)
|
||||
comment = peewee.CharField()
|
||||
|
||||
class Meta:
|
||||
table_name = 'schedules'
|
||||
database = db
|
||||
|
||||
class Backups(peewee.Model):
|
||||
directories = peewee.CharField(null=True)
|
||||
max_backups = peewee.IntegerField()
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='backups_server')
|
||||
schedule_id = peewee.ForeignKeyField(Schedules, backref='backups_schedule')
|
||||
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
database = db
|
||||
|
||||
migrator.create_table(Backups)
|
||||
migrator.create_table(Users)
|
||||
migrator.create_table(Roles)
|
||||
migrator.create_table(User_Roles)
|
||||
migrator.create_table(User_Servers)
|
||||
migrator.create_table(Host_Stats)
|
||||
migrator.create_table(Webhooks)
|
||||
migrator.create_table(Servers)
|
||||
migrator.create_table(Role_Servers)
|
||||
migrator.create_table(Server_Stats)
|
||||
migrator.create_table(Commands)
|
||||
migrator.create_table(Audit_Log)
|
||||
migrator.create_table(Schedules)
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_table('users')
|
||||
migrator.drop_table('roles')
|
||||
migrator.drop_table('user_roles')
|
||||
migrator.drop_table('audit_log') # ? Not 100% sure of the table name, please specify in the schema
|
||||
migrator.drop_table('host_stats')
|
||||
migrator.drop_table('servers')
|
||||
migrator.drop_table('user_servers')
|
||||
migrator.drop_table('role_servers')
|
||||
migrator.drop_table('server_stats')
|
||||
migrator.drop_table('commands')
|
||||
migrator.drop_table('webhooks')
|
||||
migrator.drop_table('schedules')
|
||||
migrator.drop_table('backups')
|
@ -125,8 +125,9 @@
|
||||
"sendCommand": "Send command",
|
||||
"start": "Start",
|
||||
"restart": "Restart",
|
||||
"stop": "Stop"
|
||||
},
|
||||
"stop": "Stop",
|
||||
"updating": "Updating..."
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "Players",
|
||||
"bannedPlayers": "Banned Players",
|
||||
@ -199,7 +200,23 @@
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"deleteServer": "Delete Server",
|
||||
"stopBeforeDeleting": "Please stop the server before deleting it"
|
||||
"stopBeforeDeleting": "Please stop the server before deleting it",
|
||||
"exeUpdateURLDesc": "Direct Download URL for updates.",
|
||||
"exeUpdateURL": "Server Executable Update URL",
|
||||
"update": "Update Executable",
|
||||
"bePatientUpdate": "Please be patient while we update the server. Download times can vary depending upon your internet speeds.<br /> This screen will refresh in a moment",
|
||||
"sendingRequest": "Sending your request...",
|
||||
"deleteServerQuestion": "Delete Server?",
|
||||
"deleteServerQuestionMessage": "Are you sure you want to delete this server? After this there is no going back...",
|
||||
"yesDelete": "Yes, delete",
|
||||
"noDelete": "No, go back",
|
||||
"deleteFilesQuestion": "Delete server files from machine?",
|
||||
"deleteFilesQuestionMessage": "Would you like Crafty to delete all server files from the host machine?",
|
||||
"yesDeleteFiles": "Yes, delete files",
|
||||
"noDeleteFiles": "No, just remove from panel",
|
||||
"sendingDelete": "Deleting Server",
|
||||
"bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.",
|
||||
"bePatientDeleteFiles" : "Please be patient while we remove your server from the Crafty panel and delete all files. This screen will close in a few moments."
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Server Config Area",
|
||||
|
9
main.py
9
main.py
@ -9,10 +9,11 @@ import signal
|
||||
""" Our custom classes / pip packages """
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.models import installer
|
||||
from app.classes.shared.models import installer, database
|
||||
|
||||
from app.classes.shared.tasks import TasksManager
|
||||
from app.classes.shared.controller import Controller
|
||||
from app.classes.shared.migration import MigrationManager
|
||||
|
||||
from app.classes.shared.cmd import MainPrompt
|
||||
|
||||
@ -91,16 +92,18 @@ if __name__ == '__main__':
|
||||
# our session file, helps prevent multiple controller agents on the same machine.
|
||||
helper.create_session_file(ignore=args.ignore)
|
||||
|
||||
|
||||
migration_manager = MigrationManager(database)
|
||||
migration_manager.up() # Automatically runs migrations
|
||||
|
||||
# do our installer stuff
|
||||
fresh_install = installer.is_fresh_install()
|
||||
|
||||
if fresh_install:
|
||||
console.debug("Fresh install detected")
|
||||
installer.create_tables()
|
||||
installer.default_settings()
|
||||
else:
|
||||
console.debug("Existing install detected")
|
||||
installer.check_schema_version()
|
||||
|
||||
# now the tables are created, we can load the tasks_manger and server controller
|
||||
controller = Controller()
|
||||
|
@ -23,4 +23,3 @@ termcolor==1.1.0
|
||||
tornado==6.0.4
|
||||
urllib3==1.25.10
|
||||
webencodings==0.5.1
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user