Merge branch 'dev' into enhancement/mobile-app

This commit is contained in:
Andrew 2022-08-07 12:59:14 -04:00
commit 3f4e9ac0c2
26 changed files with 1376 additions and 644 deletions

View File

@ -1,14 +1,38 @@
# Changelog
## --- [4.0.8] - 2022/TBD
## --- [4.0.10] - 2022/TBD
### New features
TBD
### Bug fixes
TBD
### Tweaks
TBD
### Lang
TBD
<br><br>
## --- [4.0.9] - 2022/08/06
### Bug fixes
- Fix Schedules Traceback Bug ([Merge Request |](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419) [Commit |](https://gitlab.com/crafty-controller/crafty-4/-/commit/f69d79b7023d6c26fccb5caeae9e47b40ebe5af2) [Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/ad318296dc93beb5533fcd13066440df9f9e799a))
- Fix handling of missing servers ([Merge Request🎉](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/420))
- Fix offline credits panel stack ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/247678e6c6af5e7d5dbfcf3bfdcec49fc5980e55))
### Tweaks
- credits-v2| Translator status ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/421))
- Use Names in Schedules ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419))
### Lang
- Make Schedules panel translatable ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419))
<br><br>
## --- [4.0.8] - 2022/08/05
### New features
- Add Crafty Version Check and notification ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/411))
### Bug fixes
- Fix SU status not sticking on user creation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/410))
- Handle Missing Java From Win Registry ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/413))
- Disable restart while server is backing up ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/414))
- Fix server creation with serverjars API ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/415))
- Fix API Key delete confirmations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/416))
### Tweaks
TBD
- Add next run to schedule info ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/417))
### Lang
- Updated `es_ES` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/412))
- Added `pl_PL` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/412))

View File

@ -2,11 +2,11 @@
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.8--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.10--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4)
[![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master)
# Crafty Controller 4.0.8-beta
# Crafty Controller 4.0.10-beta
> Python based Control Panel for your Minecraft Server
## What is Crafty Controller?

View File

@ -76,7 +76,7 @@ class ManagementController:
interval_type,
start_time,
command,
comment=None,
name,
enabled=True,
):
return HelpersManagement.create_scheduled_task(
@ -86,7 +86,7 @@ class ManagementController:
interval_type,
start_time,
command,
comment,
name,
enabled,
)

View File

@ -102,6 +102,11 @@ class ServersController(metaclass=Singleton):
server_instance.update_server_instance()
return ret
@staticmethod
def update_unloaded_server(server_obj):
ret = HelperServers.update_server(server_obj)
return ret
@staticmethod
def set_download(server_id):
srv = ServersController().get_server_instance_by_id(server_id)
@ -146,6 +151,7 @@ class ServersController(metaclass=Singleton):
def init_all_servers(self):
servers = self.get_all_defined_servers()
self.failed_servers = []
for server in servers:
server_id = server.get("server_id")
@ -169,6 +175,8 @@ class ServersController(metaclass=Singleton):
f"{server['server_name']} at path {server['path']}. "
f"Skipping this server"
)
if server not in self.failed_servers:
self.failed_servers.append(server)
continue
temp_server_dict = {

View File

@ -51,7 +51,7 @@ class ServerJars:
def get_serverjar_data(self):
data = self._read_cache()
return data.get("servers")
return data.get("types")
def _check_api_alive(self):
logger.info("Checking serverjars.com API status")
@ -70,6 +70,39 @@ class ServerJars:
logger.error("unable to contact serverjars.com api")
return False
def manual_refresh_cache(self):
cache_file = self.helper.serverjar_cache
# debug override
# cache_old = True
# if the API is down... we bomb out
if not self._check_api_alive():
return False
logger.info("Manual Refresh requested.")
now = datetime.now()
data = {
"last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"),
"types": {},
}
jar_types = self._get_server_type_list()
data["types"].update(jar_types)
for s in data["types"]:
data["types"].update({s: dict.fromkeys(data["types"].get(s), {})})
for j in data["types"].get(s):
versions = self._get_jar_details(j, s)
data["types"][s].update({j: versions})
# save our cache
try:
with open(cache_file, "w", encoding="utf-8") as f:
f.write(json.dumps(data, indent=4))
logger.info("Cache file refreshed")
except Exception as e:
logger.error(f"Unable to update serverjars.com cache file: {e}")
def refresh_cache(self):
cache_file = self.helper.serverjar_cache
@ -88,22 +121,18 @@ class ServerJars:
if cache_old:
logger.info("Cache file is over 1 day old, refreshing")
now = datetime.now()
data = {"last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"), "servers": {}}
data = {
"last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"),
"types": {},
}
jar_types = self._get_server_type_list()
# for each jar type
for j in jar_types:
# for each server
for s in jar_types.get(j):
# jar versions for this server
versions = self._get_jar_details(s)
# add these versions (a list) to the dict with
# a key of the server type
data["servers"].update({s: versions})
data["types"].update(jar_types)
for s in data["types"]:
data["types"].update({s: dict.fromkeys(data["types"].get(s), {})})
for j in data["types"].get(s):
versions = self._get_jar_details(j, s)
data["types"][s].update({j: versions})
# save our cache
try:
with open(cache_file, "w", encoding="utf-8") as f:
@ -113,8 +142,8 @@ class ServerJars:
except Exception as e:
logger.error(f"Unable to update serverjars.com cache file: {e}")
def _get_jar_details(self, jar_type="servers"):
url = f"/api/fetchAll/{jar_type}"
def _get_jar_details(self, server_type, jar_type="servers"):
url = f"/api/fetchAll/{jar_type}/{server_type}"
response = self._get_api_result(url)
temp = []
for v in response:
@ -127,19 +156,19 @@ class ServerJars:
response = self._get_api_result(url)
return response
def download_jar(self, server, version, path, server_id):
def download_jar(self, jar, server, version, path, server_id):
update_thread = threading.Thread(
name=f"server_download-{server_id}-{server}-{version}",
target=self.a_download_jar,
daemon=True,
args=(server, version, path, server_id),
args=(jar, server, version, path, server_id),
)
update_thread.start()
def a_download_jar(self, server, version, path, server_id):
def a_download_jar(self, jar, server, version, path, server_id):
# delaying download for server register to finish
time.sleep(3)
fetch_url = f"{self.base_url}/api/fetchJar/{server}/{version}"
fetch_url = f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}"
server_users = PermissionsServers.get_server_user_list(server_id)
# We need to make sure the server is registered before

View File

@ -110,11 +110,12 @@ class Schedules(BaseModel):
interval_type = CharField()
start_time = CharField(null=True)
command = CharField(null=True)
comment = CharField()
name = CharField()
one_time = BooleanField(default=False)
cron_string = CharField(default="")
parent = IntegerField(null=True)
delay = IntegerField(default=0)
next_run = CharField(default="")
class Meta:
table_name = "schedules"
@ -264,7 +265,7 @@ class HelpersManagement:
interval_type,
start_time,
command,
comment=None,
name,
enabled=True,
one_time=False,
cron_string="* * * * *",
@ -280,11 +281,12 @@ class HelpersManagement:
Schedules.interval_type: interval_type,
Schedules.start_time: start_time,
Schedules.command: command,
Schedules.comment: comment,
Schedules.name: name,
Schedules.one_time: one_time,
Schedules.cron_string: cron_string,
Schedules.parent: parent,
Schedules.delay: delay,
Schedules.next_run: "",
}
).execute()
return sch_id

View File

@ -20,6 +20,7 @@ import itertools
from datetime import datetime
from socket import gethostname
from contextlib import redirect_stderr, suppress
from packaging import version as pkg_version
from app.classes.shared.null_writer import NullWriter
from app.classes.shared.console import Console
@ -75,6 +76,7 @@ class Helpers:
self.websocket_helper = WebSocketHelper(self)
self.translation = Translation(self)
self.update_available = False
@staticmethod
def auto_installer_fix(ex):
@ -82,6 +84,29 @@ class Helpers:
print(f"Import Error: Unable to load {ex.name} module")
installer.do_install()
def check_remote_version(self):
"""
Check if the remote version is newer than the local version
Returning remote version if it is newer, otherwise False.
"""
try:
# Get tags from Gitlab, select the latest and parse the semver
response = get(
"https://gitlab.com/api/v4/projects/20430749/repository/tags"
)
if response.status_code == 200:
remote_version = pkg_version.parse(json.loads(response.text)[0]["name"])
# Get local version data from the file and parse the semver
local_version = pkg_version.parse(self.get_version_string())
if remote_version > local_version:
return remote_version
except Exception as e:
logger.error(f"Unable to check for new crafty version! \n{e}")
return False
@staticmethod
def find_java_installs():
# If we're windows return oracle java versions,

View File

@ -422,6 +422,7 @@ class Controller:
def create_jar_server(
self,
jar: str,
server: str,
version: str,
name: str,
@ -493,7 +494,7 @@ class Controller:
# download the jar
self.server_jars.download_jar(
server, version, os.path.join(server_dir, server_file), new_id
jar, server, version, os.path.join(server_dir, server_file), new_id
)
return new_id
@ -933,6 +934,14 @@ class Controller:
counter += 1
def remove_unloaded_server(self, server_id):
try:
HelpersManagement.delete_scheduled_task_by_server(server_id)
except DoesNotExist:
logger.info("No scheduled jobs exist. Continuing.")
# remove the server from the DB
self.servers.remove_server(server_id)
@staticmethod
def clear_unexecuted_commands():
HelpersManagement.clear_unexecuted_commands()

View File

@ -3,7 +3,7 @@ import time
import logging
import threading
import asyncio
import datetime
from datetime import datetime
from tzlocal import get_localzone
from tzlocal.utils import ZoneInfoNotFoundError
@ -192,6 +192,15 @@ class TasksManager:
def scheduler_thread(self):
schedules = HelpersManagement.get_schedules_enabled()
self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
self.scheduler.start()
self.check_for_updates()
self.scheduler.add_job(
self.check_for_updates,
"interval",
hours=12,
id="update_watcher",
start_date=datetime.now(),
)
# self.scheduler.add_job(
# self.scheduler.print_jobs, "interval", seconds=10, id="-1"
# )
@ -199,9 +208,10 @@ class TasksManager:
# load schedules from DB
for schedule in schedules:
if schedule.interval != "reaction":
new_job = "error"
if schedule.cron_string != "":
try:
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
CronTrigger.from_crontab(
schedule.cron_string, timezone=str(self.tz)
@ -215,6 +225,7 @@ class TasksManager:
],
)
except Exception as e:
new_job = "error"
Console.error(f"Failed to schedule task with error: {e}.")
Console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.")
@ -225,7 +236,7 @@ class TasksManager:
)
else:
if schedule.interval_type == "hours":
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
minute=0,
@ -239,7 +250,7 @@ class TasksManager:
],
)
elif schedule.interval_type == "minutes":
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
minute="*/" + str(schedule.interval),
@ -253,7 +264,7 @@ class TasksManager:
)
elif schedule.interval_type == "days":
curr_time = schedule.start_time.split(":")
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
day="*/" + str(schedule.interval),
@ -267,7 +278,18 @@ class TasksManager:
schedule.command,
],
)
self.scheduler.start()
if new_job != "error":
task = self.controller.management.get_scheduled_task_model(
int(new_job.id)
)
self.controller.management.update_scheduled_task(
task.schedule_id,
{
"next_run": str(
new_job.next_run_time.strftime("%m/%d/%Y, %H:%M:%S")
)
},
)
jobs = self.scheduler.get_jobs()
logger.info("Loaded schedules. Current enabled schedules: ")
for item in jobs:
@ -281,7 +303,7 @@ class TasksManager:
job_data["interval_type"],
job_data["start_time"],
job_data["command"],
"None",
job_data["name"],
job_data["enabled"],
job_data["one_time"],
job_data["cron_string"],
@ -296,9 +318,10 @@ class TasksManager:
# Check to see if it's enabled and is not a chain reaction.
if job_data["enabled"] and job_data["interval_type"] != "reaction":
new_job = "error"
if job_data["cron_string"] != "":
try:
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz)
@ -312,6 +335,7 @@ class TasksManager:
],
)
except Exception as e:
new_job = "error"
Console.error(f"Failed to schedule task with error: {e}.")
Console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.")
@ -320,7 +344,7 @@ class TasksManager:
self.controller.management_helper.delete_scheduled_task(sch_id)
else:
if job_data["interval_type"] == "hours":
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
minute=0,
@ -334,7 +358,7 @@ class TasksManager:
],
)
elif job_data["interval_type"] == "minutes":
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
minute="*/" + str(job_data["interval"]),
@ -348,7 +372,7 @@ class TasksManager:
)
elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":")
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
day="*/" + str(job_data["interval"]),
@ -364,6 +388,14 @@ class TasksManager:
)
logger.info("Added job. Current enabled schedules: ")
jobs = self.scheduler.get_jobs()
if new_job != "error":
task = self.controller.management.get_scheduled_task_model(
int(new_job.id)
)
self.controller.management.update_scheduled_task(
task.schedule_id,
{"next_run": new_job.next_run_time.strftime("%m/%d/%Y, %H:%M:%S")},
)
for item in jobs:
logger.info(f"JOB: {item}")
@ -416,9 +448,10 @@ class TasksManager:
)
if job_data["enabled"] and job_data["interval"] != "reaction":
new_job = "error"
if job_data["cron_string"] != "":
try:
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz)
@ -432,12 +465,13 @@ class TasksManager:
],
)
except Exception as e:
new_job = "error"
Console.error(f"Failed to schedule task with error: {e}.")
Console.info("Removing failed task from DB.")
self.controller.management_helper.delete_scheduled_task(sch_id)
else:
if job_data["interval_type"] == "hours":
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
minute=0,
@ -451,7 +485,7 @@ class TasksManager:
],
)
elif job_data["interval_type"] == "minutes":
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
minute="*/" + str(job_data["interval"]),
@ -465,7 +499,7 @@ class TasksManager:
)
elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":")
self.scheduler.add_job(
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
"cron",
day="*/" + str(job_data["interval"]),
@ -479,6 +513,14 @@ class TasksManager:
job_data["command"],
],
)
if new_job != "error":
task = self.controller.management.get_scheduled_task_model(
int(new_job.id)
)
self.controller.management.update_scheduled_task(
task.schedule_id,
{"next_run": new_job.next_run_time.strftime("%m/%d/%Y, %H:%M:%S")},
)
else:
try:
self.scheduler.get_job(str(sch_id))
@ -506,6 +548,15 @@ class TasksManager:
if task.one_time:
self.remove_job(task.schedule_id)
logger.info("one time task detected. Deleting...")
else:
self.controller.management.update_scheduled_task(
task.schedule_id,
{
"next_run": self.scheduler.get_job(
event.job_id
).next_run_time.strftime("%m/%d/%Y, %H:%M:%S")
},
)
# check for any child tasks for this. It's kind of backward,
# but this makes DB management a lot easier. One to one
# instead of one to many.
@ -606,6 +657,16 @@ class TasksManager:
)
time.sleep(1)
def check_for_updates(self):
logger.info("Checking for Crafty updates...")
self.helper.update_available = self.helper.check_remote_version()
if self.helper.update_available:
logger.info(f"Found new version {self.helper.update_available}")
else:
logger.info(
"No updates found! You are on the most up to date Crafty version."
)
def log_watcher(self):
self.controller.servers.check_for_old_logs()
self.scheduler.add_job(

View File

@ -454,6 +454,14 @@ class AjaxHandler(BaseHandler):
self.helper.backup_select(path, exec_user["user_id"])
return
elif page == "jar_cache":
if not superuser:
self.redirect("/panel/error?error=Not a super user")
return
self.controller.server_jars.manual_refresh_cache()
return
@tornado.web.authenticated
def delete(self, page):
api_key, _, exec_user = self.current_user
@ -565,9 +573,39 @@ class AjaxHandler(BaseHandler):
self.get_remote_ip(),
)
for server in self.controller.servers.failed_servers:
if server["server_id"] == int(server_id):
return
self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, True)
elif page == "delete_unloaded_server":
if not permissions["Config"] in user_perms:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument("id", None)
logger.info(
f"Removing server and all associated files for server: "
f"{self.controller.servers.get_server_friendly_name(server_id)}"
)
server_data = self.controller.servers.get_server_data_by_id(server_id)
server_name = server_data["server_name"]
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"Deleted server {server_id} named {server_name}",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.remove_all_server_tasks(server_id)
for item in self.controller.servers.failed_servers[:]:
if item["server_id"] == int(server_id):
self.controller.servers.failed_servers.remove(item)
self.controller.remove_unloaded_server(server_id)
def check_server_id(self, server_id, page_name):
if server_id is None:
logger.warning(

View File

@ -157,6 +157,10 @@ class PanelHandler(BaseHandler):
if server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return None
for server in self.controller.servers.failed_servers:
if int(server_id) == server["server_id"]:
self.failed_server = True
return server_id
# Does this server exist?
if not self.controller.servers.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
@ -207,6 +211,7 @@ class PanelHandler(BaseHandler):
@tornado.web.authenticated
async def get(self, page):
self.failed_server = False
error = self.get_argument("error", "WTF Error!")
template = "panel/denied.html"
@ -284,9 +289,10 @@ class PanelHandler(BaseHandler):
page_data: t.Dict[str, t.Any] = {
# todo: make this actually pull and compare version data
"update_available": False,
"update_available": self.helper.update_available,
"serverTZ": tz,
"version_data": self.helper.get_version_string(),
"failed_servers": self.controller.servers.failed_servers,
"user_data": exec_user,
"user_role": exec_user_role,
"user_crafty_permissions": exec_user_crafty_permissions,
@ -368,7 +374,7 @@ class PanelHandler(BaseHandler):
) as credits_default_local:
try:
remote = requests.get(
"https://craftycontrol.com/credits", allow_redirects=True
"https://craftycontrol.com/credits-v2", allow_redirects=True
)
credits_dict: dict = remote.json()
if not credits_dict["staff"]:
@ -503,9 +509,11 @@ class PanelHandler(BaseHandler):
server_id = self.check_server_id()
if server_id is None:
return
server_obj = self.controller.servers.get_server_instance_by_id(server_id)
page_data["backup_failed"] = server_obj.last_backup_status()
if not self.failed_server:
server_obj = self.controller.servers.get_server_instance_by_id(
server_id
)
page_data["backup_failed"] = server_obj.last_backup_status()
server_obj = None
valid_subpages = [
@ -517,18 +525,57 @@ class PanelHandler(BaseHandler):
"admin_controls",
"schedules",
]
server = self.controller.servers.get_server_instance_by_id(server_id)
if not self.failed_server:
server = self.controller.servers.get_server_instance_by_id(server_id)
# server_data isn't needed since the server_stats also pulls server data
page_data["server_data"] = self.controller.servers.get_server_data_by_id(
server_id
)
page_data["server_stats"] = self.controller.servers.get_server_stats_by_id(
server_id
)
page_data["downloading"] = self.controller.servers.get_download_status(
server_id
)
if not self.failed_server:
page_data[
"server_stats"
] = self.controller.servers.get_server_stats_by_id(server_id)
else:
server_temp_obj = self.controller.servers.get_server_data_by_id(
server_id
)
page_data["server_stats"] = {
"server_id": {
"server_id": server_id,
"server_name": server_temp_obj["server_name"],
"server_uuid": server_temp_obj["server_uuid"],
"path": server_temp_obj["path"],
"log_path": server_temp_obj["log_path"],
"executable": server_temp_obj["executable"],
"execution_command": server_temp_obj["execution_command"],
"stop_command": server_temp_obj["stop_command"],
"executable_update_url": server_temp_obj[
"executable_update_url"
],
"auto_start_delay": server_temp_obj["auto_start_delay"],
"server_ip": server_temp_obj["server_ip"],
"server_port": server_temp_obj["server_port"],
"logs_delete_after": server_temp_obj["logs_delete_after"],
"auto_start": server_temp_obj["auto_start"],
"crash_detection": server_temp_obj["crash_detection"],
"show_status": server_temp_obj["show_status"],
},
"running": False,
"crashed": False,
"server_type": "N/A",
"cpu": "N/A",
"mem": "N/A",
"int_ping_results": [],
"version": "N/A",
"desc": "N/A",
"started": "False",
}
if not self.failed_server:
page_data["downloading"] = self.controller.servers.get_download_status(
server_id
)
else:
page_data["downloading"] = False
page_data["server_id"] = server_id
try:
page_data["waiting_start"] = self.controller.servers.get_waiting_start(
@ -537,7 +584,10 @@ class PanelHandler(BaseHandler):
except Exception as e:
logger.error(f"Failed to get server waiting to start: {e}")
page_data["waiting_start"] = False
page_data["get_players"] = server.get_server_players()
if not self.failed_server:
page_data["get_players"] = server.get_server_players()
else:
page_data["get_players"] = []
page_data["active_link"] = subpage
page_data["permissions"] = {
"Commands": EnumPermissionsServer.COMMANDS,
@ -554,12 +604,14 @@ class PanelHandler(BaseHandler):
] = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
page_data["server_stats"]["crashed"] = self.controller.servers.is_crashed(
server_id
)
page_data["server_stats"][
"server_type"
] = self.controller.servers.get_server_type_by_id(server_id)
if not self.failed_server:
page_data["server_stats"][
"crashed"
] = self.controller.servers.is_crashed(server_id)
if not self.failed_server:
page_data["server_stats"][
"server_type"
] = self.controller.servers.get_server_type_by_id(server_id)
if subpage not in valid_subpages:
logger.debug("not a valid subpage")
if not subpage:
@ -639,6 +691,7 @@ class PanelHandler(BaseHandler):
return
page_data["java_versions"] = Helpers.find_java_installs()
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
page_data["failed"] = self.failed_server
page_java = []
page_data["java_versions"].append("java")
for version in page_data["java_versions"]:
@ -901,6 +954,7 @@ class PanelHandler(BaseHandler):
page_data["new_schedule"] = True
page_data["schedule"] = {}
page_data["schedule"]["children"] = []
page_data["schedule"]["name"] = ""
page_data["schedule"]["server_id"] = server_id
page_data["schedule"]["schedule_id"] = ""
page_data["schedule"]["action"] = ""
@ -968,6 +1022,10 @@ class PanelHandler(BaseHandler):
page_data["schedule"]["server_id"] = server_id
page_data["schedule"]["schedule_id"] = schedule.schedule_id
page_data["schedule"]["action"] = schedule.action
if schedule.name:
page_data["schedule"]["name"] = schedule.name
else:
page_data["schedule"]["name"] = ""
page_data["schedule"][
"children"
] = self.controller.management.get_child_schedules(sch_id)
@ -1466,7 +1524,15 @@ class PanelHandler(BaseHandler):
server_obj.auto_start = auto_start
server_obj.crash_detection = crash_detection
server_obj.logs_delete_after = logs_delete_after
self.controller.servers.update_server(server_obj)
failed = False
for servers in self.controller.servers.failed_servers:
if servers["server_id"] == int(server_id):
failed = True
if not failed:
self.controller.servers.update_server(server_obj)
else:
self.controller.servers.update_unloaded_server(server_obj)
self.controller.servers.init_all_servers()
self.controller.servers.crash_detection(server_obj)
self.controller.servers.refresh_server_settings(server_id)
@ -1557,6 +1623,7 @@ class PanelHandler(BaseHandler):
difficulty = bleach.clean(self.get_argument("difficulty", None))
server_obj = self.controller.servers.get_server_obj(server_id)
enabled = bleach.clean(self.get_argument("enabled", "0"))
name = bleach.clean(self.get_argument("name", ""))
if difficulty == "basic":
action = bleach.clean(self.get_argument("action", None))
interval = bleach.clean(self.get_argument("interval", None))
@ -1621,6 +1688,7 @@ class PanelHandler(BaseHandler):
if interval_type == "days":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
@ -1635,6 +1703,7 @@ class PanelHandler(BaseHandler):
}
elif difficulty == "reaction":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
@ -1650,6 +1719,7 @@ class PanelHandler(BaseHandler):
}
elif difficulty == "advanced":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": "",
@ -1665,6 +1735,7 @@ class PanelHandler(BaseHandler):
}
else:
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
@ -1714,6 +1785,7 @@ class PanelHandler(BaseHandler):
difficulty = bleach.clean(self.get_argument("difficulty", None))
server_obj = self.controller.servers.get_server_obj(server_id)
enabled = bleach.clean(self.get_argument("enabled", "0"))
name = bleach.clean(self.get_argument("name", ""))
if difficulty == "basic":
action = bleach.clean(self.get_argument("action", None))
interval = bleach.clean(self.get_argument("interval", None))
@ -1777,6 +1849,7 @@ class PanelHandler(BaseHandler):
if interval_type == "days":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
@ -1791,6 +1864,7 @@ class PanelHandler(BaseHandler):
}
elif difficulty == "advanced":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": "",
@ -1806,6 +1880,7 @@ class PanelHandler(BaseHandler):
}
elif difficulty == "reaction":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
@ -1821,6 +1896,7 @@ class PanelHandler(BaseHandler):
}
else:
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,

View File

@ -92,10 +92,12 @@ class ServerHandler(BaseHandler):
template = "public/404.html"
page_data = {
"update_available": self.helper.update_available,
"version_data": self.helper.get_version_string(),
"user_data": exec_user,
"user_role": exec_user_role,
"roles": list_roles,
"super_user": exec_user["superuser"],
"user_crafty_permissions": exec_user_crafty_permissions,
"crafty_permissions": {
"Server_Creation": EnumPermissionsCrafty.SERVER_CREATION,
@ -386,14 +388,20 @@ class ServerHandler(BaseHandler):
# deletes temp dir
FileHelpers.del_dirs(zip_path)
else:
if len(server_parts) != 2:
if len(server_parts) != 3:
self.redirect("/panel/error?error=Invalid server data")
return
server_type, server_version = server_parts
jar_type, 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
jar_type,
server_type,
server_version,
server_name,
min_mem,
max_mem,
port,
)
self.controller.management.add_to_audit_log(
exec_user["user_id"],

View File

@ -1,139 +1,238 @@
{
"staff": {
"development": [
{
"name": "Pita Bread",
"title": "CEO",
"loc": "Midwest, USA",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/craftbreadth" ], "Crafty Leadership" ],
"blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.",
"pic": "/static/assets/images/credits/pita.png"
},
{
"name": "Xithical",
"title": "CFO/COO",
"loc": "Midwest, USA",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/xithical" ], "Crafty Leadership" ],
"blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.",
"pic": "/static/assets/images/credits/xithical.png"
},
{
"name": "Phil Tarrant",
"title": "Creator",
"loc": "Southeast, USA",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/Ptarrant1" ], "Crafty Advisor" ],
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
"pic": "/static/assets/images/credits/ptarrant.png"
},
{
"name": "macgeek",
"title": "Lead Software Engineer",
"loc": "Midwest, USA",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/computergeek125" ], "Crafty Advisor" ],
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
"pic": "/static/assets/images/credits/macgeek.png"
},
{
"name": "parzivaldewey",
"title": "Support Manager",
"loc": "East Coast, USA",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/amcmanu3" ], "Support Manager" ],
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
"pic": "/static/assets/images/credits/parzivaldewey.png"
},
{
"name": "Silversthorn",
"title": "Software Engineer",
"loc": "Provence, FR",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/Silversthorn" ], null ],
"blurb": "Developer at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"",
"pic": "/static/assets/images/credits/silversthorn.png"
},
{
"name": "ThatOneLukas",
"title": "Software Engineer",
"loc": "Helsinki, FI",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/LukasDoesDev" ], null ],
"blurb": "Arch Linux enthusiast who likes 80s/90s music, Rust (The programming language) and light distros like Arch Linux btw. Dislikes C, C++, Go, and Microsoft. Also doesn't like Finnish weather that freezes molten snow to (deadly) ice.",
"pic": "/static/assets/images/credits/lukas.png"
},
{
"name": "Zedifus",
"title": "DevOps Engineer",
"loc": "Scotland, UK",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/Zedifus" ], "DevOps" ],
"blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.",
"pic": "/static/assets/images/credits/zedifus.jpg"
}
],
"support": [
{
"name": "iSilverfyre",
"title": "Community Manager",
"loc": "South Central, US",
"tags": [ "Staff", null, "Wiki" ],
"blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.",
"pic": "/static/assets/images/credits/isilverfyre.png"
},
{
"name": "Quentin",
"title": "Document Curator",
"loc": null,
"tags": [ "Staff", [ "Developer", "https://gitlab.com/qub3d" ], "Wiki" ],
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
"pic": "/static/assets/images/credits/qub3d.png"
},
{
"name": "DarthLeo",
"title": "Support Engineer",
"loc": "East Coast, US",
"tags": [ "Staff", null, "Discord" ],
"blurb": "Just a simple gamer.",
"pic": "/static/assets/images/credits/darthLeo.png"
}
],
"retired": [
{
"name": "Kev Dagoat",
"title": "Head of Development",
"loc": "East Coast, AU",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/kevdagoat" ], "HOD" ],
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.",
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
},
{
"name": "Manu",
"title": null,
"loc": "Eastern, CA",
"tags": [ "Staff", "Developer", "Project Manager" ],
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
"pic": "/static/assets/images/credits/manu.png"
},
{
"name": "UltraBlack",
"title": null,
"loc": "Bavaria, DE",
"tags": [ "Staff", null, "Idea Manager" ],
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
"pic": "/static/assets/images/credits/ultrablack.png"
},
{
"name": "MC Gaming",
"title": null,
"loc": "Central, UK",
"tags": [ "Staff", [ "Developer", "https://gitlab.com/MCgamin1738" ], null ],
"blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.",
"pic": "/static/assets/images/credits/mcgaming.png"
}
]
"development": [
{
"name": "Pita Bread",
"title": "CEO",
"loc": "Midwest, USA",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/craftbreadth"
],
"Crafty Leadership"
],
"blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.",
"pic": "/static/assets/images/credits/pita.png"
},
{
"name": "Xithical",
"title": "CFO/COO",
"loc": "Midwest, USA",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/xithical"
],
"Crafty Leadership"
],
"blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.",
"pic": "/static/assets/images/credits/xithical.png"
},
{
"name": "Phil Tarrant",
"title": "Creator",
"loc": "Southeast, USA",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/Ptarrant1"
],
"Crafty Advisor"
],
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
"pic": "/static/assets/images/credits/ptarrant.png"
},
{
"name": "macgeek",
"title": "Lead Software Engineer",
"loc": "Midwest, USA",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/computergeek125"
],
"Crafty Advisor"
],
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
"pic": "/static/assets/images/credits/macgeek.png"
},
{
"name": "pretzeldewey 🥨",
"title": "Support Manager & Software Engineer",
"loc": "East Coast, USA",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/amcmanu3"
],
"Crafty Advisor"
],
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
"pic": "/static/assets/images/credits/parzivaldewey.png"
},
{
"name": "Zedifus",
"title": "DevOps Manager",
"loc": "Scotland, UK",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/Zedifus"
],
"Crafty Advisor"
],
"blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.",
"pic": "/static/assets/images/credits/zedifus.jpg"
},
{
"name": "Silversthorn",
"title": "Software Engineer",
"loc": "Provence, FR",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/Silversthorn"
],
null
],
"blurb": "Developer at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"",
"pic": "/static/assets/images/credits/silversthorn.png"
},
{
"name": "ThatOneLukas",
"title": "Software Engineer",
"loc": "Helsinki, FI",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/LukasDoesDev"
],
null
],
"blurb": "Arch Linux enthusiast who likes 80s/90s music, Rust (The programming language) and light distros like Arch Linux btw. Dislikes C, C++, Go, and Microsoft. Also doesn't like Finnish weather that freezes molten snow to (deadly) ice.",
"pic": "/static/assets/images/credits/lukas.png"
}
],
"support": [
{
"name": "iSilverfyre",
"title": "Community Manager",
"loc": "South Central, US",
"tags": [
"Staff",
null,
"Wiki"
],
"blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.",
"pic": "/static/assets/images/credits/isilverfyre.png"
},
{
"name": "Quentin",
"title": "Document Curator",
"loc": null,
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/qub3d"
],
"Wiki"
],
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
"pic": "/static/assets/images/credits/qub3d.png"
},
{
"name": "Kornster",
"title": null,
"loc": "Victoria, Australia",
"tags": [
"Discord Mod",
null,
null
],
"blurb": "Day to day I am a Network Administrator, dealing with a range of vendors and products. Outside of work my hobbies are handyman things, such as carpentry and motor mechanics having grown up around them. My home-lab consists of Linux distros ranging from Debian, Ubuntu & Centos having self taught myself Linux for the last 12+ years.",
"pic": null
}
],
"retired": [
{
"name": "Kev Dagoat",
"title": "Head of Development",
"loc": "East Coast, AU",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/kevdagoat"
],
"HOD"
],
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.",
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
},
{
"name": "Manu",
"title": null,
"loc": "Eastern, CA",
"tags": [
"Staff",
"Developer",
"Project Manager"
],
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
"pic": "/static/assets/images/credits/manu.png"
},
{
"name": "UltraBlack",
"title": null,
"loc": "Bavaria, DE",
"tags": [
"Staff",
null,
"Idea Manager"
],
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
"pic": "/static/assets/images/credits/ultrablack.png"
},
{
"name": "MC Gaming",
"title": null,
"loc": "Central, UK",
"tags": [
"Staff",
[
"Developer",
"https://gitlab.com/MCgamin1738"
],
null
],
"blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.",
"pic": "/static/assets/images/credits/mcgaming.png"
}
]
},
"translations": {
"Data Unavailable": ["🌎🪣🔗👎😭"]
"Data Unavailable": [
{
"name": "🌎🪣🔗👎😭",
"status": false
}
]
},
"patrons": [
{
"name": "Data Unavailable 🌎🪣🔗👎😭",
"level": ""
"level": "",
"source": ""
}
],
"lastUpdate": 0

View File

@ -1,6 +1,6 @@
{
"major": 4,
"minor": 0,
"sub": 8,
"sub": 10,
"meta": "beta"
}

View File

@ -1,12 +1,27 @@
<!-- partial:partials/_footer.html -->
<footer class="footer">
<div class="container-fluid ">
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 - <span x-data x-text="new Date().getFullYear()"></span> <a href="https://craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}
</span>
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 - <span x-data x-text="new Date().getFullYear()"></span> <a href="https://craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
<span class="float-none float-sm-right d-block mt-1 mt-sm-0">&nbsp;&nbsp;{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}</span>
{% if data['update_available'] %}
<span class="float-none float-sm-right d-block mt-1 mt-sm-0"><a target="_blank" class="blink-text" href="https://gitlab.com/crafty-controller/crafty-4/-/releases">Update Available!</a></span>
{% end %}
</div>
<style>
a:hover {
text-decoration: none;
}
.blink-text{
color: #000;
font-weight: bold;
font-size: 2rem;
animation: blinkingText 2s infinite;
}
@keyframes blinkingText{
0% { color: grey;}
50% { color: red;}
100% { color: grey;}
}
</style>
</footer>
<!-- partial -->

View File

@ -342,7 +342,11 @@
<td class="pb-0">
<div class="row">
{% for language in data['translations'][person] %}
<span class="btn btn-sm btn-inverse-success mr-2" style="margin-bottom: 12px;">{{ language }}</span>
{% if language['status'] %}
<span class="btn btn-sm btn-inverse-success mr-2" style="margin-bottom: 12px;">{{ language['name'] }}</span>
{% else %}
<span class="btn btn-sm btn-inverse-secondary mr-2" style="margin-bottom: 12px;">{{ language['name'] }}</span>
{% end %}
{% end %}
</div>
</td>

View File

@ -60,12 +60,12 @@
</div>
<div class="wrapper my-auto ml-auto ml-lg-4">
<p id="cpu_data" class="mb-0 text-success" data-toggle="tooltip" data-placement="top" data-html="true"
title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}">
title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}">
{{ translate('dashboard', 'cpuUsage', data['lang']) }}: <span id="cpu_usage">{{
data.get('hosts_data').get('cpu_usage') }}</span>
</p>
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top"
title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}">
title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}">
{{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{
data.get('hosts_data').get('mem_percent') }}%</span>
</p>
@ -116,8 +116,8 @@
{% if len(data['servers']) > 0 %}
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
{% end %}
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{
@ -125,7 +125,7 @@
</div>
<div class="card-body">
{% if len(data['servers']) == 0%}
{% if len(data['servers']) == 0 and len(data['failed_servers']) == 0 %}
<div style="text-align: center; color: grey;">
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
<br>
@ -134,7 +134,7 @@
</div>
{% end %}
{% if len(data['servers']) > 0 %}
{% if len(data['servers']) > 0 or len(data['failed_servers']) > 0 %}
<!-- View for Large screen -->
<div class="table-responsive d-none d-sm-block">
<table id="servers_table" class="table table-hover">
@ -156,7 +156,7 @@
<i class="fas fa-server"></i>
{% if server['alert'] %}
<a style="color: red !important;" draggable="false"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
{{ server['server_data']['server_name'] }}&nbsp; <i class="fas fa-exclamation-triangle"></i>
</a>
{% else %}
@ -170,17 +170,17 @@
{% if server['user_command_permission'] %}
{% if server['stats']['running'] %}
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
@ -199,15 +199,15 @@
data['lang']) }}</a>
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
title="{{ translate('dashboard', 'start' , data['lang']) }}">
<i class="fas fa-play"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% end %}
@ -216,7 +216,7 @@
<td draggable="false" id="server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats']['cpu'] <= 33 %}
bg-success
@ -226,14 +226,14 @@
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
</td>
<td draggable="false" id="server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
@ -243,7 +243,7 @@
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['mem_percent']}}% -
@ -263,7 +263,7 @@
{% if server['stats']['desc'] != 'False' %}
<div id="desc_id"
style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">{{
style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">{{
server['stats']['desc'] }}</div> <br />
{% end %}
@ -287,7 +287,20 @@
{% end %}
</td>
<span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}"
data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"></span>
data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"></span>
</tr>
{% end %}
{% for server in data['failed_servers'] %}
<tr id="{{server['server_id']}}" draggable="false">
<td class="text-warning"><i class="fas fa-server"></i>&nbsp;<a class="text-warning"
href="/panel/server_detail?id={{server['server_id']}}&subpage=config">{{server['server_name']}}</a>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><i class="fas fa-cloud"></i>&nbsp;Unloaded</td>
</tr>
{% end %}
</tbody>
@ -306,27 +319,27 @@
<div class="col-10 col-lg-3 mx-0 px-0">
{% if server['alert'] %}
<a style="color: red !important" class="btn btn-link d-flex justify-content-start" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}&nbsp; <i
class="fas fa-exclamation-triangle"></i>
class="fas fa-exclamation-triangle"></i>
</a>
{% else %}
<a class="btn btn-link d-flex justify-content-start" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}
</a>
{% end %}
</div>
<div class="col-2 col-lg-3 mx-0 px-0">
<a class="btn btn-link d-flex justify-content-center" type="button" data-toggle="collapse"
data-target="#collapse-{{server['server_data']['server_id']}}" aria-expanded="false"
aria-controls="collapse-{{server['server_data']['server_id']}}">
data-target="#collapse-{{server['server_data']['server_id']}}" aria-expanded="false"
aria-controls="collapse-{{server['server_data']['server_id']}}">
<i class="fas fa-chart-bar"></i>
</a>
</div>
<div class="col-4 col-lg-3 mx-0 px-0">
<a id="m_server_running_status_{{server['server_data']['server_id']}}"
class="btn btn-link d-flex justify-content-start" type="button">
class="btn btn-link d-flex justify-content-start" type="button">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
@ -348,22 +361,22 @@
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link stop_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
class="btn btn-link stop_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link restart_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
class="btn btn-link restart_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
class="btn btn-link kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a>
</div>
@ -390,7 +403,7 @@
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link"><i
class="fa fa-spinner fa-spin"></i>
class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'downloading', data['lang']) }}</a>
</div>
</div>
@ -398,22 +411,22 @@
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn play_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
class="btn play_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
<i class="fas fa-play"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn clone_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
class="btn clone_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
class="btn kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i></a>
</div>
</div>
@ -426,14 +439,14 @@
</div>
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse"
aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
<div class="card-body">
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</h6>
<div id="m_server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats']['cpu'] <= 33 %}
bg-success
@ -443,7 +456,7 @@
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="100"></div>
aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
</div>
@ -452,7 +465,7 @@
<h6>{{ translate('dashboard', 'memUsage', data['lang']) }}</h6>
<div draggable="false" id="m_server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
@ -462,7 +475,7 @@
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['mem_percent']}}% -
@ -492,7 +505,7 @@
{% if server['stats']['desc'] != 'False' %}
<div id="desc_id"
style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">
style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">
{{ server['stats']['desc'] }}</div> <br />
{% end %}

View File

@ -15,7 +15,7 @@
<div class="page-header">
<h4 class="page-title">
{{ translate('apiKeys', 'pageTitle', data['lang']) }} - {{ data['user']['user_id'] }}
<br/>
<br />
<small>UID: {{ data['user']['user_id'] }}</small>
</h4>
</div>
@ -32,14 +32,12 @@
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config"
role="tab"
aria-selected="false">
role="tab" aria-selected="false">
<i class="fas fa-cogs"></i>{{ translate('apiKeys', 'config', data['lang']) }}</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}"
role="tab"
aria-selected="true">
role="tab" aria-selected="true">
<i class="fas fa-key"></i>{{ translate('apiKeys', 'apiKeys', data['lang']) }}</a>
</li>
</ul>
@ -48,56 +46,60 @@
<div class="col-md-7 col-sm-12">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-key"></i>{{ translate('apiKeys', 'apiKeys', data['lang']) }}</h4>
<h4 class="card-title"><i class="fas fa-key"></i>{{ translate('apiKeys', 'apiKeys',
data['lang']) }}</h4>
</div>
<div class="card-body">
<div class="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<!--<th>ID</th>-->
<th>{{ translate('apiKeys', 'name', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'created', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'superUser', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'perms', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'buttons', data['lang']) }}</th>
</tr>
<tr class="rounded">
<!--<th>ID</th>-->
<th>{{ translate('apiKeys', 'name', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'created', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'superUser', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'perms', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'buttons', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for apikey in data['api_keys'] %}
<tr>
<!--<td>{-{ apikey.token_id }-}</td>-->
<td>{{ apikey.name }}</td>
<td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td>
<td>
{% if apikey.superuser %}
<span class="text-success">
<i class="fas fa-check-square"></i> {{ translate('apiKeys', 'yes', data['lang']) }}
</span>
{% else %}
<span class="text-danger">
<i class="far fa-times-square"></i> {{ translate('apiKeys', 'no', data['lang']) }}
</span>
{% end %}
</td>
<td>{{ translate('apiKeys', 'server', data['lang']) }} {{ apikey.server_permissions }}
{{ translate('apiKeys', 'crafty', data['lang']) }} {{ apikey.crafty_permissions }}</td>
<td>
<button
class="btn btn-danger delete-api-key"
{% for apikey in data['api_keys'] %}
<tr>
<!--<td>{-{ apikey.token_id }-}</td>-->
<td>{{ apikey.name }}</td>
<td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td>
<td>
{% if apikey.superuser %}
<span class="text-success">
<i class="fas fa-check-square"></i> {{
translate('apiKeys', 'yes', data['lang']) }}
</span>
{% else %}
<span class="text-danger">
<i class="far fa-times-square"></i> {{
translate('apiKeys', 'no', data['lang']) }}
</span>
{% end %}
</td>
<td>{{ translate('apiKeys', 'server', data['lang']) }} {{
apikey.server_permissions }}
{{ translate('apiKeys', 'crafty', data['lang']) }} {{
apikey.crafty_permissions }}</td>
<td>
<button class="btn btn-danger delete-api-key"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}"
>{{ translate('panelConfig', 'delete', data['lang']) }}</button>
<button
class="btn btn-outline-primary get-a-token"
data-key-name="{{ apikey.name }}">{{
translate('panelConfig', 'delete', data['lang'])
}}</button>
<button class="btn btn-outline-primary get-a-token"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}"
>{{ translate('apiKeys', 'getToken', data['lang']) }}
</button>
</td>
</tr>
{% end %}
data-key-name="{{ apikey.name }}">{{
translate('apiKeys', 'getToken', data['lang']) }}
</button>
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
@ -109,68 +111,69 @@
<div class="col-md-5 col-sm-12">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-plus"></i> {{ translate('apiKeys', 'createNew', data['lang']) }}</h4>
<h4 class="card-title"><i class="fas fa-plus"></i> {{ translate('apiKeys',
'createNew', data['lang']) }}</h4>
</div>
<div class="card-body">
<form id="user_form" class="forms-sample" method="post"
action="/panel/edit_user_apikeys">
action="/panel/edit_user_apikeys">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
<div class="form-group">
<label class="form-label" for="username">{{ translate('apiKeys', 'name', data['lang']) }}<small
class="text-muted ml-1"> - {{ translate('apiKeys', 'nameDesc', data['lang']) }}</small> </label>
<label class="form-label" for="username">{{ translate('apiKeys', 'name',
data['lang']) }}<small class="text-muted ml-1"> - {{
translate('apiKeys', 'nameDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="name" id="name"
placeholder="API Key">
placeholder="API Key">
</div>
<table class="table table-hover mb-3">
<thead>
<tr class="rounded">
<th>{{ translate('apiKeys', 'permName', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'auth', data['lang']) }}</th>
</tr>
<tr class="rounded">
<th>{{ translate('apiKeys', 'permName', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'auth', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for permission in data['server_permissions_all'] %}
<tr>
<td><label
for="permission_{{ permission.name }}">{{ permission.name }}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
{% for permission in data['crafty_permissions_all'] %}
<tr>
<td><label
for="permission_{{ permission.name }}">{{ permission.name }}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
{% for permission in data['server_permissions_all'] %}
<tr>
<td><label for="permission_{{ permission.name }}">{{ permission.name
}}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
{% for permission in data['crafty_permissions_all'] %}
<tr>
<td><label for="permission_{{ permission.name }}">{{ permission.name
}}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
</tbody>
</table>
<label for="superuser">Superuser</label>
<input type="checkbox" class="" id="superuser"
name="superuser" value="1">
<input type="checkbox" class="" id="superuser" name="superuser" value="1">
<br/>
<br />
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-plus"></i>
Create
</button>
<button type="reset" class="btn btn-light"><i
class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}
<button type="reset" class="btn btn-light"><i class="fas fa-undo-alt"></i> {{
translate('panelConfig', 'cancel', data['lang']) }}
</button>
</form>
</div>
@ -216,15 +219,17 @@
}
},
callback: function (result) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/panel/remove_apikey?id=' + keyId,
success: function (data) {
location.reload();
},
});
if (result) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: { 'X-XSRFToken': token },
url: '/panel/remove_apikey?id=' + keyId,
success: function (data) {
location.reload();
},
});
}
}
});
})
@ -234,7 +239,7 @@
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
headers: { 'X-XSRFToken': token },
url: '/panel/get_token?id=' + keyId,
success: function (data) {
bootbox.alert({
@ -249,4 +254,4 @@
</script>
{% end %}
{% end %}

View File

@ -32,12 +32,14 @@
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
{% if not data['failed'] %}
<span class="d-none d-sm-block">
{% include "parts/server_controls_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_server_controls_list.html %}
</span>
{% end %}
<div class="row">
<div class="col-md-6 col-sm-12">
@ -117,7 +119,6 @@
</div>
<br>
{% end %}
<div class="form-group">
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang'])
@ -176,35 +177,41 @@
<div class="form-check-flat">
<label for="auto_start" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['auto_start'] %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked="" data-toggle="toggle"
value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked=""
data-toggle="toggle" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'serverAutoStart',
data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1" data-toggle="toggle">&nbsp;&nbsp;{{
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1"
data-toggle="toggle">&nbsp;&nbsp;{{
translate('serverConfig', 'serverAutoStart', data['lang']) }}
{% end %}
</label>
<label for="crash_detection" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['crash_detection'] %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" data-toggle="toggle"
checked="" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection"
data-toggle="toggle" checked="" value="1">&nbsp;&nbsp;{{ translate('serverConfig',
'serverCrashDetection', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" data-toggle="toggle"
value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection"
data-toggle="toggle" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'serverCrashDetection',
data['lang']) }}
{% end %}
</label>
{% if data['super_user'] %}
<label for="show_status" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['show_status'] %}
<input type="checkbox" class="form-check-input" id="show_status" name="show_status" data-toggle="toggle"
checked="" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="show_status" name="show_status" data-toggle="toggle"
value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus', data['lang']) }}
{% if data['super_user'] %}
<label for="show_status" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['show_status'] %}
<input type="checkbox" class="form-check-input" id="show_status" name="show_status"
data-toggle="toggle" checked="" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus',
data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="show_status" name="show_status"
data-toggle="toggle" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus',
data['lang']) }}
{% end %}
</label>
{% end %}
</label>
{% end %}
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{
@ -235,11 +242,18 @@
}}</a><br />
<small>{{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }}</small>
{% else %}
{% if not data['failed'] %}
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}</button>
{% end %}
{% if not data['failed'] %}
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig',
'deleteServer', data['lang']) }}</button>
{% else %}
<button onclick="deleteUnloadedConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig',
'deleteServer', data['lang']) }}</button>
{% end %}
{% end %}
</div>
@ -255,19 +269,19 @@
</div>
<style>
.toggle-handle {
.toggle-handle {
background-color: white !important;
}
}
.toggle-on {
.toggle-on {
color: black !important;
background-color: blueviolet !important;
}
}
.toggle {
.toggle {
height: 0px !important;
background-color: grey !important;
}
}
</style>
<!-- content-wrapper ends -->
@ -275,7 +289,7 @@
{% block js %}
<script>
$(function() {
$(function () {
$('.form-check-input').bootstrapToggle({
on: '',
off: ''
@ -430,6 +444,54 @@
}
});
}
function deleteUnloadedConfirm() {
path = "{{data['server_stats']['server_id']['path']}}";
name = "{{data['server_stats']['server_id']['server_name']}}";
bootbox.confirm({
size: "",
title: "{% raw translate('serverConfig', 'deleteServerQuestion', data['lang']) %}",
closeButton: false,
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage', data['lang']) %}",
buttons: {
confirm: {
label: "{{ translate('serverConfig', 'yesDelete', data['lang']) }}",
className: 'btn-danger',
},
cancel: {
label: "{{ translate('serverConfig', 'noDelete', data['lang']) }}",
className: 'btn-link',
}
},
callback: function (result) {
if (!result) {
return;
return;
}
else {
var token = getCookie("_xsrf")
setTimeout(function () { window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
closeButton: false
})
$.ajax({
type: "DELETE",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_unloaded_server?id=' + serverId,
data: {
},
success: function (data) {
console.log("got response:");
console.log(data);
},
});
}
}
});
}
</script>

View File

@ -9,14 +9,15 @@
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
@ -24,159 +25,212 @@
</div>
<!-- Page Title Header Ends-->
{% include "parts/details_stats.html" %}
{% include "parts/details_stats.html" %}
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
{% include "parts/server_controls_list.html" %}
{% include "parts/server_controls_list.html" %}
<div class="row">
<div class="col-md-8 col-sm-8">
{% if data['new_schedule'] == True %}
<form class="forms-sample" method="post" action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
{% else %}
<form class="forms-sample" method="post" action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
<div class="row">
<div class="col-md-8 col-sm-8">
{% if data['new_schedule'] == True %}
<form class="forms-sample" method="post"
action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
{% else %}
<form class="forms-sample" method="post"
action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
{% end %}
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="difficulty">Basic / Cron / Chain-Reaction Select<small class="text-muted ml-1"></small> </label><br>
<select id="difficulty" name="difficulty" onchange="basicAdvanced(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['difficulty'] }}">
<option id="basic" value="basic">{{ translate('serverScheduleConfig', 'basic' , data['lang']) }}</option>
<option id="advanced" value="advanced">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }}</option>
<option id="reaction" value="reaction">{{ translate('serverScheduleConfig', 'reaction' , data['lang']) }}</option>
</select>
</div>
<div class="form-group">
<label for="server_name">Action<small class="text-muted ml-1"></small> </label><br>
<select id="action" name="action" onchange="yesnoCheck(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
<option id="start" value="start">{{ translate('serverScheduleConfig', 'start' , data['lang']) }}</option>
<option id="restart" value="restart">{{ translate('serverScheduleConfig', 'restart' , data['lang']) }}</option>
<option id="stop" value="stop">{{ translate('serverScheduleConfig', 'stop' , data['lang']) }}</option>
<option id="backup" value="backup">{{ translate('serverScheduleConfig', 'backup' , data['lang']) }}</option>
<option id="command" value="command">{{ translate('serverScheduleConfig', 'custom' , data['lang']) }}</option>
</select>
</div>
<div id="ifBasic">
<div class="form-group">
<label for="server_path">{{ translate('serverScheduleConfig', 'interval' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' , data['lang']) }}</small> </label>
<input type="number" class="form-control" name="interval" id="interval" value="{{ data['schedule']['interval'] }}" placeholder="Interval" required>
<br>
<br>
<select id="interval_type" onchange="ifDays(this);" name="interval_type" class="form-control form-control-lg select-css" value="{{ data['schedule']['interval_type'] }}">
<option id = "days" value="days">{{ translate('serverScheduleConfig', 'days' , data['lang']) }}</option>
<option id = "hours" value="hours">{{ translate('serverScheduleConfig', 'hours' , data['lang']) }}</option>
<option id = "minutes" value="minutes">{{ translate('serverScheduleConfig', 'minutes' , data['lang']) }}</option>
</select>
</div>
<div id="ifDays" style="display: block;">
<div class="form-group">
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' , data['lang']) }}</small> </label>
<input type="time" class="form-control" name="time" id="time" value="{{ data['schedule']['time'] }}" placeholder="Time" required>
</div>
</div>
</div>
<div id="ifYes" style="display: none;">
<div class="form-group">
<label for="command">{{ translate('serverScheduleConfig', 'command' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'command-explain' , data['lang']) }}</small> </label>
<input type="input" class="form-control" name="command" id="command_input" value="{{ data['schedule']['command'] }}" placeholder="Command" required>
</div>
<div class="form-group">
<label for="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label>
<input type="input" class="form-control" name="name" id="name_input"
value="{{ data['schedule']['name']}}" maxlength="30" placeholder="Name" required>
</div>
<div id="ifAdvanced" style="display: none;">
<div class="form-group">
<label for="difficulty">{{ translate('serverScheduleConfig', 'select' , data['lang']) }}<small
class="text-muted ml-1"></small> </label><br>
<select id="difficulty" name="difficulty" onchange="basicAdvanced(this);"
class="form-control form-control-lg select-css" value="{{ data['schedule']['difficulty'] }}">
<option id="basic" value="basic">{{ translate('serverScheduleConfig', 'basic' , data['lang']) }}
</option>
<option id="advanced" value="advanced">{{ translate('serverScheduleConfig', 'cron' , data['lang'])
}}</option>
<option id="reaction" value="reaction">{{ translate('serverScheduleConfig', 'reaction' ,
data['lang']) }}</option>
</select>
</div>
<div class="form-group">
<label for="server_name">{{ translate('serverSchedules', 'action' , data['lang']) }}<small
class="text-muted ml-1"></small> </label><br>
<select id="action" name="action" onchange="yesnoCheck(this);"
class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
<option id="start" value="start">{{ translate('serverScheduleConfig', 'start' , data['lang']) }}
</option>
<option id="restart" value="restart">{{ translate('serverScheduleConfig', 'restart' ,
data['lang']) }}</option>
<option id="stop" value="stop">{{ translate('serverScheduleConfig', 'stop' , data['lang']) }}
</option>
<option id="backup" value="backup">{{ translate('serverScheduleConfig', 'backup' , data['lang'])
}}</option>
<option id="command" value="command">{{ translate('serverScheduleConfig', 'custom' , data['lang'])
}}</option>
</select>
</div>
<div id="ifBasic">
<div class="form-group">
<label for="server_path">{{ translate('serverScheduleConfig', 'interval' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' ,
data['lang']) }}</small> </label>
<input type="number" class="form-control" name="interval" id="interval"
value="{{ data['schedule']['interval'] }}" placeholder="Interval" required>
<br>
<br>
<select id="interval_type" onchange="ifDays(this);" name="interval_type"
class="form-control form-control-lg select-css"
value="{{ data['schedule']['interval_type'] }}">
<option id="days" value="days">{{ translate('serverScheduleConfig', 'days' , data['lang']) }}
</option>
<option id="hours" value="hours">{{ translate('serverScheduleConfig', 'hours' , data['lang']) }}
</option>
<option id="minutes" value="minutes">{{ translate('serverScheduleConfig', 'minutes' ,
data['lang']) }}</option>
</select>
</div>
<div id="ifDays" style="display: block;">
<div class="form-group">
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang']) }}</small> </label>
<input type="input" class="form-control" name="cron" id="cron" value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' ,
data['lang']) }}</small> </label>
<input type="time" class="form-control" name="time" id="time"
value="{{ data['schedule']['time'] }}" placeholder="Time" required>
</div>
</div>
<div id="ifReaction" style="display: none;">
<div class="form-group">
<label for="delay">{{ translate('serverScheduleConfig', 'offset' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'offset-explain' , data['lang']) }}</small> </label>
<input type="number" class="form-control" name="delay" id="delay" value="0">
<br>
<br>
<label for="parent">{{ translate('serverScheduleConfig', 'parent' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'parent-explain' , data['lang']) }}</small> </label>
<select id="parent" name="parent" class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
</div>
<div id="ifYes" style="display: none;">
<div class="form-group">
<label for="command">{{ translate('serverScheduleConfig', 'command' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'command-explain' ,
data['lang']) }}</small> </label>
<input type="input" class="form-control" name="command" id="command_input"
value="{{ data['schedule']['command'] }}" placeholder="Command" required>
</div>
</div>
<div id="ifAdvanced" style="display: none;">
<div class="form-group">
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang'])
}}</small> </label>
<input type="input" class="form-control" name="cron" id="cron"
value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
</div>
</div>
<div id="ifReaction" style="display: none;">
<div class="form-group">
<label for="delay">{{ translate('serverScheduleConfig', 'offset' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'offset-explain' ,
data['lang']) }}</small> </label>
<input type="number" class="form-control" name="delay" id="delay" value="0">
<br>
<br>
<label for="parent">{{ translate('serverScheduleConfig', 'parent' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'parent-explain' ,
data['lang']) }}</small> </label>
<select id="parent" name="parent" class="form-control form-control-lg select-css"
value="{{ data['schedule']['action'] }}">
{% for schedule in data['schedules'] %}
{% if schedule.schedule_id != data['schedule']['schedule_id'] %}
{% if schedule.interval != '' %}
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">ID: {{schedule.schedule_id}} | {{schedule.command}} | {{schedule.interval}} {{ schedule.interval_type}}</option>
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
{{schedule.name}} | {{schedule.command}} | {{schedule.interval}} {{
schedule.interval_type}}</option>
{% else %}
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">ID: {{schedule.schedule_id}} {{schedule.command}} {{schedule.cron_string}}</option>
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
{{schedule.name}} | {{schedule.command}} | {{schedule.cron_string}}</option>
{% end %}
{% end %}
{% end %}
</select>
</div>
</div>
</div>
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">{{ translate('serverScheduleConfig', 'enabled' , data['lang']) }}
</label>
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked=""
value="1">{{ translate('serverScheduleConfig', 'enabled' , data['lang']) }}
</label>
</div>
<div class="form-check-flat">
<label for="one_time" class="form-check-label ml-4 mb-4">
<input type="checkbox" class="form-check-input" id="one_time" name="one_time" value="1">{{ translate('serverScheduleConfig', 'one-time' , data['lang']) }}
</label>
</div>
<div class="form-check-flat">
<label for="one_time" class="form-check-label ml-4 mb-4">
<input type="checkbox" class="form-check-input" id="one_time" name="one_time" value="1">{{
translate('serverScheduleConfig', 'one-time' , data['lang']) }}
</label>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('serverConfig', 'save', data['lang']) }}</button>
<button type="reset" onclick="location.href=`/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules`" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
</form>
</div>
<div class="col-sm-4 grid-margin">
<h4>{{ translate('serverScheduleConfig', 'children' , data['lang']) }}</h4>
<ul>
{% for schedule in data['schedule']['children'] %}
<li style="overflow: auto;"><a href="/panel/edit_schedule?id={{schedule.server_id}}&sch_id={{schedule.schedule_id}}">{{schedule.schedule_id}} | {{schedule.command}}</a></li>
{% end %}
</ul>
</div>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{
translate('serverConfig', 'save', data['lang']) }}</button>
<button type="reset"
onclick="location.href=`/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules`"
class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel',
data['lang']) }}</button>
</form>
</div>
<div class="col-sm-4 grid-margin">
<h4>{{ translate('serverScheduleConfig', 'children' , data['lang']) }}</h4>
<ul>
{% for schedule in data['schedule']['children'] %}
<li style="overflow: auto;"><a
href="/panel/edit_schedule?id={{schedule.server_id}}&sch_id={{schedule.schedule_id}}">{{schedule.name}}
| {{schedule.schedule_id}}</a></li>
{% end %}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
{% block js %}
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$( document ).ready(function() {
console.log( "ready!" );
$(document).ready(function () {
console.log("ready!");
});
function yesnoCheck() {
if (document.getElementById('action').value == "command") {
document.getElementById("ifYes").style.display = "block";
document.getElementById("command_input").required = true;
document.getElementById("ifYes").style.display = "block";
document.getElementById("command_input").required = true;
} else {
document.getElementById("ifYes").style.display = "none";
document.getElementById("command_input").required = false;
document.getElementById("ifYes").style.display = "none";
document.getElementById("command_input").required = false;
}
}
function basicAdvanced() {
if (document.getElementById('difficulty').value == "advanced") {
}
function basicAdvanced() {
if (document.getElementById('difficulty').value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifReaction").style.display = "none";
document.getElementById("ifBasic").style.display = "none";
@ -184,16 +238,16 @@
document.getElementById("parent").required = false;
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
} else if(document.getElementById('difficulty').value == "reaction"){
document.getElementById("ifReaction").style.display = "block";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("delay").required = true;
document.getElementById("parent").required = true;
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
}
else {
} else if (document.getElementById('difficulty').value == "reaction") {
document.getElementById("ifReaction").style.display = "block";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("delay").required = true;
document.getElementById("parent").required = true;
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
}
else {
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifReaction").style.display = "none";
document.getElementById("ifBasic").style.display = "block";
@ -201,67 +255,67 @@
document.getElementById("parent").required = false;
document.getElementById("interval").required = true;
document.getElementById("time").required = true;
}
}
function ifDays() {
if (document.getElementById('interval_type').value == "days") {
document.getElementById("ifDays").style.display = "block";
document.getElementById("time").required = true;
} else {
document.getElementById("ifDays").style.display = "none";
document.getElementById("time").required = false;
}
}
}
function ifDays() {
if (document.getElementById('interval_type').value == "days") {
document.getElementById("ifDays").style.display = "block";
document.getElementById("time").required = true;
} else {
document.getElementById("ifDays").style.display = "none";
document.getElementById("time").required = false;
}
}
function del_task(sch_id, id){
var token = getCookie("_xsrf")
function del_task(sch_id, id) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id,
data: {
schedule_id: sch_id,
id: id
},
success: function(data) {
location.reload();
},
});
}
function startup(){
try{
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);
}catch{
console.log("no element named")
}
try{
document.getElementById("{{ data['schedule']['difficulty'] }}").setAttribute('selected', true);
}catch{
console.log("no element named")
}
try{
document.getElementById("{{ data['schedule']['action'] }}").setAttribute('selected', true);
$.ajax({
type: "DELETE",
headers: { 'X-XSRFToken': token },
url: '/ajax/del_task?server_id=' + id + '&schedule_id=' + sch_id,
data: {
schedule_id: sch_id,
id: id
},
success: function (data) {
location.reload();
},
});
}
function startup() {
try {
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);
} catch {
console.log("no element named")
}
try {
document.getElementById("{{ data['schedule']['difficulty'] }}").setAttribute('selected', true);
} catch {
console.log("no element named")
}
try {
document.getElementById("{{ data['schedule']['action'] }}").setAttribute('selected', true);
}catch{
console.log("no element named")
}
ifDays();
yesnoCheck();
basicAdvanced();
if("{{ data['schedule']['enabled'] }}" == 'True'){
document.getElementById('enabled').checked = true;
}else{
document.getElementById('enabled').checked = false;
}
if("{{ data['schedule']['one_time'] }}" == 'True'){
document.getElementById('one_time').checked = true;
}else{
document.getElementById('one_time').checked = false;
}
}
} catch {
console.log("no element named")
}
ifDays();
yesnoCheck();
basicAdvanced();
if ("{{ data['schedule']['enabled'] }}" == 'True') {
document.getElementById('enabled').checked = true;
} else {
document.getElementById('enabled').checked = false;
}
if ("{{ data['schedule']['one_time'] }}" == 'True') {
document.getElementById('one_time').checked = true;
} else {
document.getElementById('one_time').checked = false;
}
}
window.onload(startup())
</script>
window.onload(startup())
</script>
{% end %}
{% end %}

View File

@ -14,7 +14,8 @@
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
@ -43,35 +44,44 @@
<div class="col-md-12 col-sm-12" style="overflow-x:auto;">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-calendar"></i> Scheduled Tasks</h4>
<h4 class="card-title"><i class="fas fa-calendar"></i> {{ translate('serverSchedules',
'scheduledTasks', data['lang']) }} </h4>
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" ,
data-placement="bottom"></span>
data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" ,
data-placement="bottom"></span>
{% end %}
<div><button
onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`"
class="btn btn-info">Create New Schedule <i class="fas fa-pencil-alt"></i></button></div>
onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`"
class="btn btn-info">{{ translate('serverSchedules', 'create', data['lang']) }}<i
class="fas fa-pencil-alt"></i></button></div>
</div>
<div class="card-body">
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%"
style="table-layout:fixed;">
style="table-layout:fixed;">
<thead>
<tr class="rounded">
<th style="width: 2%; min-width: 10px;">ID</th>
<th style="width: 23%; min-width: 50px;">Action</th>
<th style="width: 40%; min-width: 50px;">Command</th>
<th style="width: 10%; min-width: 50px;">Interval</th>
<th style="width: 10%; min-width: 50px;">Start Time</th>
<th style="width: 10%; min-width: 50px;">Enabled</th>
<th style="width: 10%; min-width: 50px;">Edit</th>
<th style="width: 2%; min-width: 10px;">{{ translate('serverSchedules', 'name', data['lang']) }}
</th>
<th style="width: 23%; min-width: 50px;">{{ translate('serverSchedules', 'action', data['lang'])
}}</th>
<th style="width: 40%; min-width: 50px;">{{ translate('serverSchedules', 'command',
data['lang']) }}</th>
<th style="width: 10%; min-width: 50px;">{{ translate('serverSchedules', 'interval',
data['lang']) }}</th>
<th style="width: 10%; min-width: 50px;">{{ translate('serverSchedules', 'nextRun',
data['lang']) }}</th>
<th style="width: 10%; min-width: 50px;">{{ translate('serverSchedules', 'enabled',
data['lang']) }}</th>
<th style="width: 10%; min-width: 50px;">{{ translate('serverSchedules', 'edit', data['lang'])
}}</th>
</tr>
</thead>
<tbody>
{% for schedule in data['schedules'] %}
<tr>
<td id="{{schedule.schedule_id}}" class="id">
<p>{{schedule.schedule_id}}</p>
<p>{{schedule.name}}</p>
</td>
<td id="{{schedule.action}}" class="action">
<p>{{schedule.action}}</p>
@ -81,23 +91,28 @@
</td>
<td id="{{schedule.interval}}" class="action">
{% if schedule.interval != '' %}
<p>Every</p>
<p>{{ translate('serverSchedules', 'every', data['lang']) }}</p>
<p>{{schedule.interval}} {{schedule.interval_type}}</p>
{% elif schedule.interval_type == 'reaction' %}
<p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p>
<p>{{schedule.interval_type}}<br><br>{{ translate('serverSchedules', 'child', data['lang'])}}:
{{ schedule.parent }}</p>
{% else %}
<p>Cron String:</p>
<p>{{schedule.cron_string}}</p>
{% end %}
</td>
<td id="{{schedule.start_time}}" class="action">
<p>{{schedule.start_time}}</p>
<p>{{schedule.next_run}}</p>
</td>
<td id="{{schedule.enabled}}" class="action">
<input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
<input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle"
data-schedule-id="{{schedule.schedule_id}}"
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
</td>
<td id="{{schedule.action}}" class="action">
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
<button
onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'"
class="btn btn-info">
<i class="fas fa-pencil-alt"></i>
</button>
<br>
@ -111,12 +126,16 @@
</tbody>
</table>
<hr />
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" style="table-layout:fixed;">
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%"
style="table-layout:fixed;">
<thead>
<tr class="rounded">
<th style="width: 25%; min-width: 50px;">Action</th>
<th style="max-width: 40%; min-width: 50px;">Command</th>
<th style="width: 10%; min-width: 50px;">Enabled</th>
<th style="width: 25%; min-width: 50px;">{{ translate('serverSchedules', 'action', data['lang'])
}}</th>
<th style="max-width: 40%; min-width: 50px;">{{ translate('serverSchedules', 'command',
data['lang']) }}</th>
<th style="width: 10%; min-width: 50px;">{{ translate('serverSchedules', 'enabled',
data['lang']) }}</th>
</tr>
</thead>
<tbody>
@ -131,21 +150,23 @@
<td id="{{schedule.enabled}}" class="action">
{% if schedule.enabled %}
<span class="text-success">
<i class="fas fa-check-square"></i> Yes
<i class="fas fa-check-square"></i> {{ translate('serverSchedules', 'yes', data['lang']) }}
</span>
{% else %}
<span class="text-danger">
<i class="far fa-times-square"></i> No
<i class="far fa-times-square"></i> {{ translate('serverSchedules', 'no', data['lang']) }}
</span>
{% end %}
</td>
</tr>
<!-- Modal -->
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Task Details</h5>
<h5 class="modal-title" id="exampleModalLabel">{{ translate('serverSchedules', 'details',
data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@ -153,47 +174,62 @@
<div class="modal-body">
<ul style="list-style: none;">
<li id="{{schedule.schedule_id}}" class="id" style="border-top: .1em solid gray;">
<h4>ID</h4>
<h4>{{ translate('serverSchedules', 'name', data['lang']) }}</h4>
<p>{{schedule.schedule_id}}</p>
</li>
<li id="{{schedule.action}}" class="action" style="border-top: .1em solid gray;">
<h4>Action</h4>
<h4>{{ translate('serverSchedules', 'action', data['lang']) }}</h4>
<p>{{schedule.action}}</p>
</li>
<li id="{{schedule.command}}" class="action" style="border-top: .1em solid gray;">
<h4>Command</h4>
<h4>{{ translate('serverSchedules', 'command', data['lang']) }}</h4>
<p>{{schedule.command}}</p>
</li>
<li id="{{schedule.interval}}" class="action" style="border-top: .1em solid gray;">
{% if schedule.interval != '' %}
<h4>Interval</h4>
<p>Every {{schedule.interval}} {{schedule.interval_type}}</p>
<h4>{{ translate('serverSchedules', 'interval', data['lang']) }}</h4>
<p>{{ translate('serverSchedules', 'every', data['lang']) }} {{schedule.interval}}
{{schedule.interval_type}}</p>
{% elif schedule.interval_type == 'reaction' %}
<h4>Interval</h4>
<p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p>
<h4>{{ translate('serverSchedules', 'interval', data['lang']) }}</h4>
<p>{{schedule.interval_type}}<br><br>{{ translate('serverSchedules', 'child',
data['lang']) }}: {{ schedule.parent }}</p>
{% else %}
<h4>Interval</h4>
<p>Cron String: {{schedule.cron_string}}</p>
<h4>{{ translate('serverSchedules', 'interval', data['lang']) }}</h4>
<p>{{ translate('serverSchedules', 'cron', data['lang']) }}: {{schedule.cron_string}}
</p>
{% end %}
</li>
<li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;">
<h4>Start Time</h4>
<p>{{schedule.start_time}}</p>
<h4>{{ translate('serverSchedules', 'nextRun', data['lang']) }}</h4>
{% if schedule.next_run %}
<p>{{schedule.next_run}}</p>
{% else %}
<p>zzzzzzz</p>
{% end %}
</li>
<li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
<h4>Enabled</h4>
<input type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
<li id="{{schedule.enabled}}" class="action"
style="border-top: .1em solid gray; border-bottom: .1em solid gray">
<h4>{{ translate('serverSchedules', 'enabled', data['lang']) }}</h4>
<input type="checkbox" class="schedule-enabled-toggle"
data-schedule-id="{{schedule.schedule_id}}"
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
</li>
</ul>
</div>
<div class="modal-footer">
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
<i class="fas fa-pencil-alt"></i> Edit
<button
onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'"
class="btn btn-info">
<i class="fas fa-pencil-alt"></i> {{ translate('serverSchedules', 'edit', data['lang'])
}}
</button>
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i> Delete
<i class="fas fa-trash" aria-hidden="true"></i> {{ translate('serverSchedules',
'delete', data['lang']) }}
</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverSchedules', 'close', data['lang']) }}</button>
</div>
</div>
</div>
@ -215,12 +251,15 @@
color: white !important;
;
}
.toggle-handle {
background-color: white !important;
}
.toggle-on {
color: black !important;
}
.toggle {
height: 0px !important;
}
@ -250,7 +289,7 @@
{% block js %}
<script>
function debounce(func, timeout = 300){
function debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
@ -265,17 +304,17 @@
onstyle: 'success',
offstyle: 'danger',
})
$('.schedule-enabled-toggle').each(function() {
$('.schedule-enabled-toggle').each(function () {
const enabled = JSON.parse(this.getAttribute('data-schedule-enabled'));
$(this).bootstrapToggle(enabled ? 'on' : 'off')
})
$('.schedule-enabled-toggle').change(function() {
$('.schedule-enabled-toggle').change(function () {
const id = this.getAttribute('data-schedule-id');
const enabled = this.checked;
fetch(`/api/v2/servers/{{data['server_id']}}/tasks/${id}`, {
method: 'PATCH',
body: JSON.stringify({enabled}),
body: JSON.stringify({ enabled }),
headers: {
'Content-Type': 'application/json',
},
@ -288,7 +327,7 @@
$(document).ready(function () {
console.log('ready for JS!')
$('#schedule_table').DataTable({
'order': [4, 'desc']
'order': [4, 'asc'],
}
);
@ -296,7 +335,7 @@
$(document).ready(function () {
console.log('ready for JS!')
$('#mini_schedule_table').DataTable({
'order': [2, 'desc']
'order': [2, 'asc']
}
);
document.getElementById('mini_schedule_table_wrapper').hidden = true;
@ -420,4 +459,4 @@
</script>
{% end %}
{% end %}

View File

@ -32,13 +32,32 @@
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="server_type">{{ translate('serverWizard', 'serverType', data['lang']) }}</label>
<select required class="form-control form-control-lg select-css" id="server_type" name="server_type"
onchange="serverTypeChange(this)">
<option value="">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
{% for s in data['server_types'] %}
<option value="{{ s }}">{{ s.capitalize() }}</option>
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang'])
}}</label>
{% if data['super_user'] %}
<select style="width: 90%;" required class="form-control form-control-lg select-css" id="server_jar"
name="server_jar" onchange="serverJarChange(this)">
{% else %}
<select required class="form-control form-control-lg select-css" id="server_jar" name="server_jar"
onchange="serverJarChange(this)">
{% end %}
<option value="None">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
{% for s in data['server_types'] %}
<option value="{{ s }}">{{ s.capitalize() }}</option>
{% end %}
</select>
{% if data['super_user'] %}
&nbsp;&nbsp;<i onclick="refreshCache()" id="refresh-cache" class="refresh-class fas fa-sync"></i>
{% end %}
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_type">{{ translate('serverWizard', 'serverSelect', data['lang']) }}</label>
<select required class="form-control form-control-lg select-css" id="server_type" name="server_type"
onchange="serverTypeChange(this)">
<option value="">{{ translate('serverWizard', 'selectServer', data['lang']) }}</option>
</select>
</div>
</div>
@ -56,14 +75,14 @@
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name"
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
</div>
<br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4>
<hr>
<div class="row">
@ -73,7 +92,7 @@
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5"
min="0.5" required>
min="0.5" required>
</div>
</div>
@ -82,7 +101,7 @@
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5"
min="0.5" required>
min="0.5" required>
</div>
</div>
@ -91,7 +110,7 @@
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1"
required>
required>
</div>
</div>
<div class="col-sm-12">
@ -100,7 +119,7 @@
<div class="card">
<div class="card-header p-2" id="Role-1">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true"
aria-controls="collapseRole-1">
aria-controls="collapseRole-1">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
@ -111,7 +130,7 @@
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
type="checkbox">&nbsp;
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
@ -152,7 +171,7 @@
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value=""
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
@ -161,7 +180,7 @@
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{
translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path"
placeholder="/var/opt/server" required>
placeholder="/var/opt/server" required>
</div>
</div>
@ -169,7 +188,7 @@
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
placeholder="paper.jar" required>
placeholder="paper.jar" required>
</div>
</div>
@ -178,7 +197,7 @@
</div>
<br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4>
<hr>
<div class="row">
@ -188,7 +207,7 @@
<label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory2" name="min_memory" value="1" step="0.5"
min="0.5" required>
min="0.5" required>
</div>
</div>
@ -197,7 +216,7 @@
<label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory2" name="max_memory" value="2" step="0.5"
min="0.5" required>
min="0.5" required>
</div>
</div>
@ -206,7 +225,7 @@
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1"
required>
required>
</div>
</div>
<div class="col-sm-12">
@ -215,7 +234,7 @@
<div class="card">
<div class="card-header p-2" id="Role-2">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true"
aria-controls="collapseRole-2">
aria-controls="collapseRole-2">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
@ -226,7 +245,7 @@
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
type="checkbox">&nbsp;
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
@ -266,7 +285,7 @@
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value=""
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
@ -275,7 +294,7 @@
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{
translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path"
placeholder="/var/opt/server.zip" required>
placeholder="/var/opt/server.zip" required>
</div>
</div>
@ -294,7 +313,7 @@
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
placeholder="paper.jar" required>
placeholder="paper.jar" required>
</div>
</div>
</div>
@ -303,7 +322,7 @@
<div class="col-sm-3">
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4>
<hr>
<div class="row">
@ -313,7 +332,7 @@
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" step="0.5"
min="0.5" required>
min="0.5" required>
</div>
</div>
@ -322,7 +341,7 @@
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" step="0.5"
min="0.5" required>
min="0.5" required>
</div>
</div>
@ -331,7 +350,7 @@
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1"
required>
required>
</div>
</div>
@ -341,7 +360,7 @@
<div class="card">
<div class="card-header p-2" id="Role-3">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3"
aria-expanded="true" aria-controls="collapseRole-3">
aria-expanded="true" aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang'])
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
@ -352,7 +371,7 @@
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
type="checkbox">&nbsp;
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
@ -368,7 +387,7 @@
</div>
</div>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
aria-hidden="true">
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
@ -380,7 +399,7 @@
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path=""
style="overflow: scroll; max-height:75%;">
style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input" name="root_path" value="" checked>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
@ -401,7 +420,7 @@
</div>
</div>
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
}}</button>
@ -414,6 +433,10 @@
</div>
</div>
<style>
.refresh-class:hover {
cursor: grab;
}
.scroll {
max-height: 12em;
overflow-y: auto;
@ -673,6 +696,25 @@
});
}
function refreshCache() {
var token = getCookie("_xsrf")
document.getElementById("refresh-cache").classList.add("fa-spin")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/jar_cache',
success: function () {
document.getElementById("refresh-cache").classList.remove("fa-sync");
document.getElementById("refresh-cache").classList.remove("fa-spin");
document.getElementById("refresh-cache").classList.add("fa-check");
setTimeout(function () {
location.reload();
}, 2000);
},
});
}
</script>
<script type="text/javascript">
var text = '{% raw data["js_server_types"] %}';
@ -682,11 +724,21 @@
*/
function serverTypeChange(selectObj) {
// get the index of the selected option
var idx = selectObj.selectedIndex;
var idx = document.getElementById('server_type').selectedIndex;
// get the value of the selected option
var which = selectObj.options[idx].value;
var cSelect = document.getElementById("server");
try {
var which = document.getElementById('server_type').options[idx].value;
} catch {
while (cSelect.options.length > 0) {
cSelect.remove(0);
}
return;
}
let server_type = which.split('|')[0];
let server = which.split('|')[1];
// use the selected option value to retrieve the list of items from the serverTypesLists array
cList = serverTypesLists[which];
let cList = serverTypesLists[server_type];
// get the country select element via its known id
var cSelect = document.getElementById("server");
// remove the current options from the country select
@ -696,7 +748,7 @@
}
var newOption;
// create new options ordered by ascending
cList.forEach(type => {
cList[server].forEach(type => {
newOption = document.createElement("option");
newOption.value = which + "|" + type; // assumes option string and value are the same
newOption.text = type;
@ -709,5 +761,47 @@
}
})
}
function serverJarChange(selectObj) {
let type_select = document.getElementById('server_jar')
let tidx = type_select.selectedIndex;
let val = type_select.options[tidx].value;
if (val == 'None') {
var jcSelect = document.getElementById("server_type");
while (jcSelect.options.length > 0) {
jcSelect.remove(0);
}
serverTypeChange(selectObj);
return;
}
// get the index of the selected option
var jidx = selectObj.selectedIndex;
// get the value of the selected option
var jwhich = selectObj.options[jidx].value;
// use the selected option value to retrieve the list of items from the serverTypesLists array
jcList = Object.keys(serverTypesLists[jwhich]);
// get the country select element via its known id
var jcSelect = document.getElementById("server_type");
// remove the current options from the country select
var jlen = jcSelect.options.length;
while (jcSelect.options.length > 0) {
jcSelect.remove(0);
}
var jnewOption;
// create new options ordered by ascending
jcList.forEach(type => {
jnewOption = document.createElement("option");
jnewOption.value = jwhich + "|" + type; // assumes option string and value are the same
jnewOption.text = type;
// add the new option
try {
jcSelect.add(jnewOption); // this will fail in DOM browsers but is needed for IE
}
catch (e) {
jcSelect.appendChild(jnewOption);
}
})
serverTypeChange(selectObj);
}
</script>
{% end %}

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("schedules", next_run=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("schedules", ["next_run"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,17 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.rename_column("schedules", "comment", "name")
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.rename_column("schedules", "name", "comment")
"""
Write your rollback migrations here.
"""

View File

@ -48,7 +48,7 @@
"subscriptionLevel": "Level",
"supportTeam": "Support and Documentation Team",
"thankYou": "THANK YOU",
"translationDesc": "to our community who translate!",
"translationDesc": "to our community who translate! [ Active = 🟢 Inactive/Retired = ⚪ ]",
"translationName": "Name",
"translationTitle": "Language Translation",
"translator": "Translators"
@ -392,6 +392,7 @@
},
"serverScheduleConfig": {
"backup": "Backup Server",
"select": "Basic / Cron / Chain Reaction Select",
"basic": "Basic",
"children": "Linked Child Tasks: ",
"command": "Command",
@ -418,7 +419,24 @@
"time-explain": "What time do you want your schedule to execute?"
},
"serverSchedules": {
"scheduledTasks": "Scheduled Tasks",
"create": "Create New Schedule",
"name": "Name",
"action": "Action",
"command": "Command",
"interval": "Interval",
"nextRun": "Next Run",
"enabled": "Enabled",
"edit": "Edit",
"every": "Every",
"yes": "Yes",
"no": "No",
"cron": "Crong String",
"details": "Schedule Details",
"child": "Child of schedule with ID ",
"areYouSure": "Delete Scheduled Task?",
"close": "Close",
"delete": "Delete",
"cancel": "Cancel",
"cannotSee": "Not seeing everything?",
"cannotSeeOnMobile": "Try clicking on a scheduled task for full details.",
@ -480,7 +498,8 @@
"save": "Save",
"selectRole": "Select Role(s)",
"selectRoot": "Select Archive Root Dir",
"selectType": "Select a Type",
"selectType": "Server Type (Vanilla, Servers, Modded, etc.)",
"selectServer": "Select a Server",
"selectVersion": "Select a Version",
"selectZipDir": "Select the directory in the archive you want us to unzip files from",
"serverJar": "Server Executable File",
@ -488,6 +507,7 @@
"serverPath": "Server Path",
"serverPort": "Server Port",
"serverType": "Server Type",
"serverSelect": "Server Select",
"serverVersion": "Server Version",
"sizeInGB": "Size in GB",
"zipPath": "Server Path"

16
main.py
View File

@ -7,8 +7,9 @@ import argparse
import logging.config
import signal
import peewee
from app.classes.shared.file_helpers import FileHelpers
from packaging import version as pkg_version
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.import3 import Import3
from app.classes.shared.console import Console
from app.classes.shared.helpers import Helpers
@ -225,6 +226,19 @@ if __name__ == "__main__":
controller_setup_thread.join()
Console.info("Crafty has fully started and is now ready for use!")
# Check if new version available
remote_ver = helper.check_remote_version()
if remote_ver:
notice = f"""
A new version of Crafty is available!
{'/' * 37}
New version available: {remote_ver}
Current version: {pkg_version.parse(helper.get_version_string())}
{'/' * 37}
"""
Console.yellow(notice)
crafty_prompt.prompt = f"Crafty Controller v{helper.get_version_string()} > "
try:
logger.info("Removing old temp dirs")