mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into tweak/file-enhancements
This commit is contained in:
commit
610b94dd59
@ -21,7 +21,7 @@ win-dev-build:
|
||||
- pyinstaller -F main.py
|
||||
--distpath .
|
||||
--icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico
|
||||
--name "crafty_commander"
|
||||
--name "crafty"
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
@ -37,7 +37,7 @@ win-dev-build:
|
||||
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
- .\crafty.exe
|
||||
exclude:
|
||||
- app\classes\**\*
|
||||
|
||||
@ -63,7 +63,7 @@ win-prod-build:
|
||||
- pyinstaller -F main.py
|
||||
--distpath .
|
||||
--icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico
|
||||
--name "crafty_commander"
|
||||
--name "crafty"
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
@ -81,7 +81,7 @@ win-prod-build:
|
||||
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
- .\crafty.exe
|
||||
expire_in: never
|
||||
exclude:
|
||||
- app\classes\**\*
|
||||
|
@ -232,6 +232,8 @@ function-naming-style=snake_case
|
||||
good-names=e,
|
||||
ex,
|
||||
f,
|
||||
fd,
|
||||
fn,
|
||||
i,
|
||||
id,
|
||||
ip,
|
||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@ -1,43 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## [4.0.3] - TBD
|
||||
## --- [4.0.4] - 2022/06/21
|
||||
|
||||
### New features
|
||||
TBD
|
||||
|
||||
- Add shutdown on backup feature ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/373))
|
||||
- Add detection and dropdown of java versions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/375))
|
||||
### Bug fixes
|
||||
TBD
|
||||
|
||||
- Backup/Config.json rework for API key hardening ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/369))
|
||||
- Fix stack on ping result being falsy ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/371))
|
||||
- Fix sec bug with server creation roles ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/376))
|
||||
### Tweaks
|
||||
- Use 4 space indentation for the session file.
|
||||
- Use with-blocks when opening files to prevent them from being left open
|
||||
- Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/370))
|
||||
- Backup failure warning (Tab text goes red) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/373))
|
||||
- Rework server list on dashboard display for use on small screens ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/372))
|
||||
- File handling enhancements ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/362))
|
||||
<br><br>
|
||||
|
||||
## [4.0.2] - 2022/06/16
|
||||
## --- [4.0.3] - 2022/06/18
|
||||
### New features
|
||||
- Integrate Wiki iframe into panel instead of link ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/367))
|
||||
### Bug fixes
|
||||
- Amend Java system variable fix to be more specfic since they only affect Oracle. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/364))
|
||||
- API Token authentication hardening ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/364))
|
||||
### Tweaks
|
||||
- Add better error logging for statistic collection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/359))
|
||||
<br><br>
|
||||
|
||||
## --- [4.0.2-hotfix1] - 2022/06/17
|
||||
### Crit Bug fixes
|
||||
- Fix blank server_detail page for general users ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/358))
|
||||
<br><br>
|
||||
|
||||
## --- [4.0.2] - 2022/06/16
|
||||
### New features
|
||||
None
|
||||
|
||||
### Bug fixes
|
||||
- Fix winreg import pass on non-NT systems ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/344))
|
||||
- Make the WebSocket automatically reconnect. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/345))
|
||||
- Fix an error when there are no servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/346))
|
||||
- Use relative paths for the jarfile and logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/347))
|
||||
- Flatten all instances of username creation or editing, usernames should be lower case.
|
||||
- - ([Merge Request 1](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342))
|
||||
- - ([Merge Request 2](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/351))
|
||||
- Add version inheretence & config check ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/353))
|
||||
- Fix support log temp file deletion issue/hang ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/354))
|
||||
<br><br>
|
||||
|
||||
## [4.0.1] - 2022/06/15
|
||||
|
||||
## --- [4.0.1] - 2022/06/15
|
||||
### New features
|
||||
None
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Remove session.lock warning ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/338))
|
||||
- Correct Dutch Spacing Issue ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/340))
|
||||
- Remove no-else-* pylint exemptions and tidy code. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342))
|
||||
- Make unRAID more readable, and flatten path to lower, to fit standard practice. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/337))
|
||||
- Fix Java Pathing issues on windows ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/343/diffs?commit_id=cda2120579083d447db5dbeb5489822880f4cae7))
|
||||
|
||||
|
68
CONTRIBUTING.md
Normal file
68
CONTRIBUTING.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Crafty 4 - A contributors guide.
|
||||
*Don't Panic!*<br><br>
|
||||
|
||||
First off, thank you for choosing Crafty Controller! <br>
|
||||
We hope you've been enjoying the beta so far and are absolutely thrilled that you are looking to contribute!
|
||||
|
||||
The following guide will show you how to easily and safely contribute to our current workflow. There are a few components that need to be taken into account and processes that need followed before we can merge your code into our repository.
|
||||
<br><br>
|
||||
## Getting started
|
||||
There are two incredibly helpful ways of contributing to the project: `Issues` and `Changes`.
|
||||
|
||||
### Issues
|
||||
|
||||
#### Create a new issue
|
||||
|
||||
If you spot a problem with crafty, [search if an issue already exists](https://gitlab.com/crafty-controller/crafty-4/-/issues). If a related issue doesn't exist, you can [open a new issue](https://gitlab.com/crafty-controller/crafty-4/-/issues/new) using a relevant issue template:
|
||||
- Bug - For any bugs you may find in Crafty.
|
||||
- Feature Request - For any features you'd like to see in Crafty.
|
||||
- Change Request - For any changes you'd like to see to existing Crafty functions.
|
||||
|
||||
#### Solve an issue
|
||||
|
||||
If you're feeling inclined and want to help us with our workload you can have a look through our [existing issues](https://gitlab.com/crafty-controller/crafty-4/-/issues) to find one that interests you. (You can narrow down the search using `labels` as filters.)
|
||||
|
||||
### Make changes
|
||||
|
||||
1. [Install Git](https://docs.gitlab.com/ee/topics/git/how_to_install_git/).
|
||||
|
||||
2. Fork the repository.
|
||||
|
||||
3. Create a branch from `dev` with a suitable name that matches our folder branch flow<br> `bugfix/` `tweak/` `lang/` `feature/`<br>
|
||||
For Example:<br>
|
||||
`tweak/websocket-auto-reconnect`<br>
|
||||
`bugfix/blank-page-as-non-superuser`<br>
|
||||
`lang/german-spelling-correction`<br>
|
||||
`feature/support-log-downloader`
|
||||
|
||||
4. Make your changes!
|
||||
|
||||
5. Make sure your code is formatted correctly ([We use Black](https://black.readthedocs.io/en/stable/getting_started.html)).
|
||||
> 🧑🎓 If you are using **VSCODE** you can follow this [handy dandy tiny guide](https://marcobelo.medium.com/setting-up-python-black-on-visual-studio-code-5318eba4cd00) on how to setup formatting on save.<br>
|
||||
This will allow you to write your code without having to think about ⬛**black**, and then when you press `ctrl+s` black will immediately format your code!
|
||||
|
||||
|
||||
### Commit your update
|
||||
|
||||
Commit your changes once you are happy with them. See [Chris Beam's guide](https://chris.beams.io/posts/git-commit/) on how to write a suitable commit message. This will be enforced. If your commit messages don't meet suitable standards, your merge request will not be merged.
|
||||
|
||||
- Please make sure and test the area that you have been working in, this makes our reviewers' lives easier!
|
||||
|
||||
### Create a merge request
|
||||
|
||||
Once you are all done making your changes make a MR (merge request) into our `dev` branch.
|
||||
|
||||
- Fill in the merge request template. This template helps reviewers understand your changes as well as the purpose of your merge request. Make sure to include details!
|
||||
- If you are solving an issue don't forget to link the MR to that issue.
|
||||
- Make sure to [allow upstream commits](https://docs.gitlab.com/ee/user/project/merge_requests/allow_collaboration.html#allow-commits-from-upstream-members) so we can prepare the branch for merge if it's not quite right. <br> A member of the maintainer team will review your proposal. We may also ask questions or request additional information at this stage. On some occasions we may reject your proposal, please don't be disheartened if we do so. Even if your code does not make it into the repo we appreciate your time and effort spent on creating the MR.
|
||||
- Please make sure your merge request complies with the pylint's Code Climate report on your MR and fix any issues that are raised.
|
||||
- We may ask for changes to be made before a MR can be merged, either using [suggested changes](https://docs.gitlab.com/ee/user/project/merge_requests/reviews/suggestions.html), inline comments or merge request threads. You can apply suggested changes directly through GitLab's UI. You can also make any other changes in your fork, then commit them to your branch before the merge request is processed.
|
||||
- As you update your MR with changes we request, mark each thread as [resolved](https://docs.gitlab.com/ee/user/discussions/#resolve-a-thread).
|
||||
- If you run into any merge issues checkout this [git tutorial](https://about.gitlab.com/blog/2016/09/06/resolving-merge-conflicts-from-the-gitlab-ui/) to help you resolve them. (If you get stuck your reviewer can help you.)
|
||||
|
||||
### Your MR is merged!
|
||||
|
||||
Congratulations 🎉 You've successfully made a contribution to Crafty!
|
||||
|
||||
|
||||
|
@ -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.2--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
|
||||
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.4--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.2-beta
|
||||
# Crafty Controller 4.0.4-beta
|
||||
> Python based Control Panel for your Minecraft Server
|
||||
|
||||
## What is Crafty Controller?
|
||||
@ -39,7 +39,7 @@ With `Crafty Controller 4.0` we have focused on building our DevOps Principles,
|
||||
> __**⚠ 🔻WARNING: [WSL/WSL2 | WINDOWS 11 | DOCKER DESKTOP]🔻**__ <br>
|
||||
BE ADVISED! Upstream is currently broken for Minecraft running on **Docker under WSL/WSL2, Windows 11 / DOCKER DESKTOP!** <br>
|
||||
On '**Stop**' or '**Restart**' of the MC Server, there is a 90% chance the World's Chunks will be shredded irreparably! <br>
|
||||
Please only run Docker on Linux, If you are using Windows we have a portable installs found here: [Latest-Stable](https://gitlab.com/crafty-controller/crafty-4/-/jobs/artifacts/master/download?job=win-prod-build), [Latest-Development](https://gitlab.com/crafty-controller/crafty-4/-/jobs/artifacts/dev/download?job=win-dev-build)
|
||||
Please only run Docker on Linux, If you are using Windows we have a portable installs found here: [Latest-Stable](https://gitlab.com/crafty-controller/crafty-4/-/releases), [Latest-Development](https://gitlab.com/crafty-controller/crafty-4/-/jobs/artifacts/dev/download?job=win-dev-build)
|
||||
|
||||
----
|
||||
|
||||
|
@ -17,6 +17,14 @@ class ManagementController:
|
||||
def get_latest_hosts_stats():
|
||||
return HelpersManagement.get_latest_hosts_stats()
|
||||
|
||||
@staticmethod
|
||||
def set_crafty_api_key(key):
|
||||
HelpersManagement.set_secret_api_key(key)
|
||||
|
||||
@staticmethod
|
||||
def get_crafty_api_key():
|
||||
return HelpersManagement.get_secret_api_key()
|
||||
|
||||
# **********************************************************************************
|
||||
# Commands Methods
|
||||
# **********************************************************************************
|
||||
@ -128,9 +136,10 @@ class ManagementController:
|
||||
max_backups: int = None,
|
||||
excluded_dirs: list = None,
|
||||
compress: bool = False,
|
||||
shutdown: bool = False,
|
||||
):
|
||||
return self.management_helper.set_backup_config(
|
||||
server_id, backup_path, max_backups, excluded_dirs, compress
|
||||
server_id, backup_path, max_backups, excluded_dirs, compress, shutdown
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -5,6 +5,7 @@ import json
|
||||
import typing as t
|
||||
|
||||
from app.classes.controllers.roles_controller import RolesController
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
|
||||
from app.classes.shared.singleton import Singleton
|
||||
from app.classes.shared.server import ServerInstance
|
||||
@ -28,8 +29,9 @@ logger = logging.getLogger(__name__)
|
||||
class ServersController(metaclass=Singleton):
|
||||
servers_list: ServerInstance
|
||||
|
||||
def __init__(self, helper, servers_helper, management_helper):
|
||||
def __init__(self, helper, servers_helper, management_helper, file_helper):
|
||||
self.helper: Helpers = helper
|
||||
self.file_helper: FileHelpers = file_helper
|
||||
self.servers_helper: HelperServers = servers_helper
|
||||
self.management_helper = management_helper
|
||||
self.servers_list = []
|
||||
@ -189,6 +191,7 @@ class ServersController(metaclass=Singleton):
|
||||
self.helper,
|
||||
self.management_helper,
|
||||
self.stats,
|
||||
self.file_helper,
|
||||
),
|
||||
"server_settings": settings.props,
|
||||
}
|
||||
|
@ -63,7 +63,9 @@ class Stats:
|
||||
psutil.boot_time(), datetime.timezone.utc
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"error while getting boot time due to {e}")
|
||||
logger.debug(
|
||||
"getting boot time failed due to the following error:", exc_info=e
|
||||
)
|
||||
# unix epoch with no timezone data
|
||||
return datetime.datetime.fromtimestamp(0, datetime.timezone.utc)
|
||||
|
||||
@ -72,7 +74,9 @@ class Stats:
|
||||
try:
|
||||
return psutil.cpu_percent(interval=0.5) / psutil.cpu_count()
|
||||
except Exception as e:
|
||||
logger.debug(f"error while getting cpu percentage due to {e}")
|
||||
logger.debug(
|
||||
"getting the cpu usage failed due to the following error:", exc_info=e
|
||||
)
|
||||
return -1
|
||||
|
||||
def __init__(self, helper, controller):
|
||||
@ -100,7 +104,9 @@ class Stats:
|
||||
"disk_data": Stats._try_all_disk_usage(),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.debug(f"error while getting host stats due to {e}")
|
||||
logger.debug(
|
||||
"getting host stats failed due to the following error:", exc_info=e
|
||||
)
|
||||
node_stats: NodeStatsDict = {
|
||||
"boot_time": str(
|
||||
datetime.datetime.fromtimestamp(0, datetime.timezone.utc)
|
||||
@ -124,21 +130,27 @@ class Stats:
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _try_get_process_stats(process):
|
||||
def _try_get_process_stats(process, running):
|
||||
if running:
|
||||
try:
|
||||
return Stats._get_process_stats(process)
|
||||
except Exception as e:
|
||||
logger.debug(f"error while getting process stats due to {e}")
|
||||
logger.debug(
|
||||
f"getting process stats for pid {process.pid} "
|
||||
"failed due to the following error:",
|
||||
exc_info=e,
|
||||
)
|
||||
return {"cpu_usage": -1, "memory_usage": -1, "mem_percentage": -1}
|
||||
else:
|
||||
return {"cpu_usage": 0, "memory_usage": 0, "mem_percentage": 0}
|
||||
|
||||
@staticmethod
|
||||
def _get_process_stats(process):
|
||||
if process is None:
|
||||
return {"cpu_usage": 0, "memory_usage": 0, "mem_percentage": 0}
|
||||
return {"cpu_usage": -1, "memory_usage": -1, "mem_percentage": -1}
|
||||
process_pid = process.pid
|
||||
try:
|
||||
p = psutil.Process(process_pid)
|
||||
dummy = p.cpu_percent()
|
||||
_dummy = p.cpu_percent()
|
||||
|
||||
# call it first so we can be more accurate per the docs
|
||||
# https://giamptest.readthedocs.io/en/latest/#psutil.Process.cpu_percent
|
||||
@ -149,25 +161,19 @@ class Stats:
|
||||
with p.oneshot():
|
||||
process_stats = {
|
||||
"cpu_usage": real_cpu,
|
||||
"memory_usage": Helpers.human_readable_file_size(
|
||||
p.memory_info()[0]
|
||||
),
|
||||
"memory_usage": Helpers.human_readable_file_size(p.memory_info()[0]),
|
||||
"mem_percentage": round(p.memory_percent(), 0),
|
||||
}
|
||||
return process_stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Unable to get process details for pid: {process_pid} Error: {e}"
|
||||
)
|
||||
return {"cpu_usage": 0, "memory_usage": 0, "mem_percentage": 0}
|
||||
|
||||
@staticmethod
|
||||
def _try_all_disk_usage():
|
||||
try:
|
||||
return Stats._all_disk_usage()
|
||||
except Exception as e:
|
||||
logger.debug(f"error while getting disk data due to {e}")
|
||||
logger.debug(
|
||||
"getting disk stats failed due to the following error:", exc_info=e
|
||||
)
|
||||
return []
|
||||
|
||||
# Source: https://github.com/giampaolo/psutil/blob/master/scripts/disk_usage.py
|
||||
@ -212,7 +218,7 @@ class Stats:
|
||||
|
||||
return level_total_size
|
||||
|
||||
def get_server_players(self, server_id): # pylint: disable=no-self-use
|
||||
def get_server_players(self, server_id):
|
||||
|
||||
server = HelperServers.get_server_data_by_id(server_id)
|
||||
|
||||
@ -246,15 +252,20 @@ class Stats:
|
||||
online_stats = json.loads(ping_obj.players)
|
||||
|
||||
except Exception as e:
|
||||
logger.info(f"Unable to read json from ping_obj: {e}")
|
||||
logger.info(
|
||||
"Unable to read json from ping_obj due to the following error:",
|
||||
exc_info=e,
|
||||
)
|
||||
|
||||
try:
|
||||
server_icon = base64.encodebytes(ping_obj.icon)
|
||||
server_icon = server_icon.decode("utf-8")
|
||||
except Exception as e:
|
||||
server_icon = False
|
||||
logger.info(f"Unable to read the server icon : {e}")
|
||||
|
||||
logger.info(
|
||||
"Unable to read the server icon due to the following error:", exc_info=e
|
||||
)
|
||||
if ping_obj:
|
||||
ping_data = {
|
||||
"online": online_stats.get("online", 0),
|
||||
"max": online_stats.get("max", 0),
|
||||
@ -263,6 +274,15 @@ class Stats:
|
||||
"server_version": ping_obj.version,
|
||||
"server_icon": server_icon,
|
||||
}
|
||||
else:
|
||||
ping_data = {
|
||||
"online": online_stats.get("online", 0),
|
||||
"max": online_stats.get("max", 0),
|
||||
"players": online_stats.get("players", 0),
|
||||
"server_description": "",
|
||||
"server_version": "",
|
||||
"server_icon": server_icon,
|
||||
}
|
||||
|
||||
return ping_data
|
||||
|
||||
@ -273,7 +293,9 @@ class Stats:
|
||||
server_icon = base64.encodebytes(ping_obj["icon"])
|
||||
except Exception as e:
|
||||
server_icon = False
|
||||
logger.info(f"Unable to read the server icon : {e}")
|
||||
logger.info(
|
||||
"Unable to read the server icon due to the following error:", exc_info=e
|
||||
)
|
||||
ping_data = {
|
||||
"online": ping_obj["server_player_count"],
|
||||
"max": ping_obj["server_player_max"],
|
||||
|
@ -38,6 +38,16 @@ class AuditLog(BaseModel):
|
||||
table_name = "audit_log"
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# Crafty Settings Class
|
||||
# **********************************************************************************
|
||||
class CraftySettings(BaseModel):
|
||||
secret_api_key = CharField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = "crafty_settings"
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# Host_Stats Class
|
||||
# **********************************************************************************
|
||||
@ -118,6 +128,7 @@ class Backups(BaseModel):
|
||||
max_backups = IntegerField()
|
||||
server_id = ForeignKeyField(Servers, backref="backups_server")
|
||||
compress = BooleanField(default=False)
|
||||
shutdown = BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "backups"
|
||||
@ -231,6 +242,17 @@ class HelpersManagement:
|
||||
else:
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def set_secret_api_key(key):
|
||||
CraftySettings.insert(secret_api_key=key).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_secret_api_key():
|
||||
settings = CraftySettings.select(CraftySettings.secret_api_key).where(
|
||||
CraftySettings.id == 1
|
||||
)
|
||||
return settings[0].secret_api_key
|
||||
|
||||
# **********************************************************************************
|
||||
# Schedules Methods
|
||||
# **********************************************************************************
|
||||
@ -330,6 +352,7 @@ class HelpersManagement:
|
||||
"max_backups": row.max_backups,
|
||||
"server_id": row.server_id_id,
|
||||
"compress": row.compress,
|
||||
"shutdown": row.shutdown,
|
||||
}
|
||||
except IndexError:
|
||||
conf = {
|
||||
@ -338,6 +361,7 @@ class HelpersManagement:
|
||||
"max_backups": 0,
|
||||
"server_id": server_id,
|
||||
"compress": False,
|
||||
"shutdown": False,
|
||||
}
|
||||
return conf
|
||||
|
||||
@ -348,6 +372,7 @@ class HelpersManagement:
|
||||
max_backups: int = None,
|
||||
excluded_dirs: list = None,
|
||||
compress: bool = False,
|
||||
shutdown: bool = False,
|
||||
):
|
||||
logger.debug(f"Updating server {server_id} backup config with {locals()}")
|
||||
if Backups.select().where(Backups.server_id == server_id).exists():
|
||||
@ -359,6 +384,7 @@ class HelpersManagement:
|
||||
"max_backups": 0,
|
||||
"server_id": server_id,
|
||||
"compress": False,
|
||||
"shutdown": False,
|
||||
}
|
||||
new_row = True
|
||||
if max_backups is not None:
|
||||
@ -367,6 +393,7 @@ class HelpersManagement:
|
||||
dirs_to_exclude = ",".join(excluded_dirs)
|
||||
conf["excluded_dirs"] = dirs_to_exclude
|
||||
conf["compress"] = compress
|
||||
conf["shutdown"] = shutdown
|
||||
if not new_row:
|
||||
with self.database.atomic():
|
||||
if backup_path is not None:
|
||||
|
@ -5,6 +5,7 @@ import jwt
|
||||
from jwt import PyJWTError
|
||||
|
||||
from app.classes.models.users import HelperUsers, ApiKeys
|
||||
from app.classes.controllers.management_controller import ManagementController
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -13,11 +14,14 @@ class Authentication:
|
||||
def __init__(self, helper):
|
||||
self.helper = helper
|
||||
self.secret = "my secret"
|
||||
self.secret = self.helper.get_setting("apikey_secret", None)
|
||||
|
||||
if self.secret is None or self.secret == "random":
|
||||
try:
|
||||
self.secret = ManagementController.get_crafty_api_key()
|
||||
if self.secret == "":
|
||||
self.secret = self.helper.random_string_generator(64)
|
||||
self.helper.set_setting("apikey_secret", self.secret)
|
||||
ManagementController.set_crafty_api_key(str(self.secret))
|
||||
except:
|
||||
self.secret = self.helper.random_string_generator(64)
|
||||
ManagementController.set_crafty_api_key(str(self.secret))
|
||||
|
||||
def generate(self, user_id, extra=None):
|
||||
if extra is None:
|
||||
|
@ -2,14 +2,22 @@ import os
|
||||
import shutil
|
||||
import logging
|
||||
import pathlib
|
||||
import tempfile
|
||||
import zipfile
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
|
||||
from app.classes.shared.helpers import Helpers
|
||||
from app.classes.shared.console import Console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileHelpers:
|
||||
allowed_quotes = ['"', "'", "`"]
|
||||
|
||||
def __init__(self, helper):
|
||||
self.helper: Helpers = helper
|
||||
|
||||
@staticmethod
|
||||
def del_dirs(path):
|
||||
path = pathlib.Path(path)
|
||||
@ -82,7 +90,6 @@ class FileHelpers:
|
||||
f"Error backing up: {os.path.join(root, file)}!"
|
||||
f" - Error was: {e}"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
@ -113,3 +120,173 @@ class FileHelpers:
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def make_compressed_backup(
|
||||
self, path_to_destination, path_to_zip, excluded_dirs, server_id
|
||||
):
|
||||
# create a ZipFile object
|
||||
path_to_destination += ".zip"
|
||||
ex_replace = [p.replace("\\", "/") for p in excluded_dirs]
|
||||
total_bytes = 0
|
||||
dir_bytes = Helpers.get_dir_size(path_to_zip)
|
||||
results = {
|
||||
"percent": 0,
|
||||
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||
}
|
||||
self.helper.websocket_helper.broadcast_page_params(
|
||||
"/panel/server_detail",
|
||||
{"id": str(server_id)},
|
||||
"backup_status",
|
||||
results,
|
||||
)
|
||||
with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file:
|
||||
for root, dirs, files in os.walk(path_to_zip, topdown=True):
|
||||
for l_dir in dirs:
|
||||
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
|
||||
dirs.remove(l_dir)
|
||||
ziproot = path_to_zip
|
||||
for file in files:
|
||||
if (
|
||||
str(os.path.join(root, file)).replace("\\", "/")
|
||||
not in ex_replace
|
||||
and file != "crafty.sqlite"
|
||||
):
|
||||
try:
|
||||
logger.info(f"backing up: {os.path.join(root, file)}")
|
||||
if os.name == "nt":
|
||||
zip_file.write(
|
||||
os.path.join(root, file),
|
||||
os.path.join(root.replace(ziproot, ""), file),
|
||||
)
|
||||
else:
|
||||
zip_file.write(
|
||||
os.path.join(root, file),
|
||||
os.path.join(root.replace(ziproot, "/"), file),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Error backing up: {os.path.join(root, file)}!"
|
||||
f" - Error was: {e}"
|
||||
)
|
||||
total_bytes += os.path.getsize(os.path.join(root, file))
|
||||
percent = round((total_bytes / dir_bytes) * 100, 2)
|
||||
results = {
|
||||
"percent": percent,
|
||||
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||
}
|
||||
self.helper.websocket_helper.broadcast_page_params(
|
||||
"/panel/server_detail",
|
||||
{"id": str(server_id)},
|
||||
"backup_status",
|
||||
results,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def make_backup(self, path_to_destination, path_to_zip, excluded_dirs, server_id):
|
||||
# create a ZipFile object
|
||||
path_to_destination += ".zip"
|
||||
ex_replace = [p.replace("\\", "/") for p in excluded_dirs]
|
||||
total_bytes = 0
|
||||
dir_bytes = Helpers.get_dir_size(path_to_zip)
|
||||
results = {
|
||||
"percent": 0,
|
||||
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||
}
|
||||
self.helper.websocket_helper.broadcast_page_params(
|
||||
"/panel/server_detail",
|
||||
{"id": str(server_id)},
|
||||
"backup_status",
|
||||
results,
|
||||
)
|
||||
with ZipFile(path_to_destination, "w") as zip_file:
|
||||
for root, dirs, files in os.walk(path_to_zip, topdown=True):
|
||||
for l_dir in dirs:
|
||||
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
|
||||
dirs.remove(l_dir)
|
||||
ziproot = path_to_zip
|
||||
for file in files:
|
||||
if (
|
||||
str(os.path.join(root, file)).replace("\\", "/")
|
||||
not in ex_replace
|
||||
and file != "crafty.sqlite"
|
||||
):
|
||||
try:
|
||||
logger.info(f"backing up: {os.path.join(root, file)}")
|
||||
if os.name == "nt":
|
||||
zip_file.write(
|
||||
os.path.join(root, file),
|
||||
os.path.join(root.replace(ziproot, ""), file),
|
||||
)
|
||||
else:
|
||||
zip_file.write(
|
||||
os.path.join(root, file),
|
||||
os.path.join(root.replace(ziproot, "/"), file),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Error backing up: {os.path.join(root, file)}!"
|
||||
f" - Error was: {e}"
|
||||
)
|
||||
total_bytes += os.path.getsize(os.path.join(root, file))
|
||||
percent = round((total_bytes / dir_bytes) * 100, 2)
|
||||
results = {
|
||||
"percent": percent,
|
||||
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||
}
|
||||
self.helper.websocket_helper.broadcast_page_params(
|
||||
"/panel/server_detail",
|
||||
{"id": str(server_id)},
|
||||
"backup_status",
|
||||
results,
|
||||
)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def unzip_file(zip_path):
|
||||
new_dir_list = zip_path.split("/")
|
||||
new_dir = ""
|
||||
for i in range(len(new_dir_list) - 1):
|
||||
if i == 0:
|
||||
new_dir += new_dir_list[i]
|
||||
else:
|
||||
new_dir += "/" + new_dir_list[i]
|
||||
|
||||
if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path):
|
||||
Helpers.ensure_dir_exists(new_dir)
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
for i in enumerate(zip_ref.filelist):
|
||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
|
||||
i
|
||||
].filename.endswith("/"):
|
||||
break
|
||||
|
||||
full_root_path = temp_dir
|
||||
|
||||
for item in os.listdir(full_root_path):
|
||||
if os.path.isdir(os.path.join(full_root_path, item)):
|
||||
try:
|
||||
FileHelpers.move_dir(
|
||||
os.path.join(full_root_path, item),
|
||||
os.path.join(new_dir, item),
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
||||
else:
|
||||
try:
|
||||
FileHelpers.move_file(
|
||||
os.path.join(full_root_path, item),
|
||||
os.path.join(new_dir, item),
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
||||
except Exception as ex:
|
||||
Console.error(ex)
|
||||
else:
|
||||
return "false"
|
||||
return
|
||||
|
@ -15,6 +15,8 @@ import html
|
||||
import zipfile
|
||||
import pathlib
|
||||
import ctypes
|
||||
import subprocess
|
||||
import itertools
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
from contextlib import redirect_stderr, suppress
|
||||
@ -22,7 +24,6 @@ from contextlib import redirect_stderr, suppress
|
||||
from app.classes.shared.null_writer import NullWriter
|
||||
from app.classes.shared.console import Console
|
||||
from app.classes.shared.installer import installer
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
from app.classes.shared.translation import Translation
|
||||
from app.classes.web.websocket_helper import WebSocketHelper
|
||||
|
||||
@ -81,6 +82,60 @@ class Helpers:
|
||||
print(f"Import Error: Unable to load {ex.name} module")
|
||||
installer.do_install()
|
||||
|
||||
@staticmethod
|
||||
def find_java_installs():
|
||||
# If we're windows return oracle java versions,
|
||||
# otherwise java vers need to be manual.
|
||||
if os.name == "nt":
|
||||
# Adapted from LeeKamentsky >>>
|
||||
# https://github.com/LeeKamentsky/python-javabridge/blob/master/javabridge/locate.py
|
||||
jdk_key_paths = (
|
||||
"SOFTWARE\\JavaSoft\\JDK",
|
||||
"SOFTWARE\\JavaSoft\\Java Development Kit",
|
||||
)
|
||||
java_paths = []
|
||||
for jdk_key_path in jdk_key_paths:
|
||||
try:
|
||||
with suppress(OSError), winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE, jdk_key_path
|
||||
) as kjdk:
|
||||
for i in itertools.count():
|
||||
version = winreg.EnumKey(kjdk, i)
|
||||
kjdk_current = winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
jdk_key_path,
|
||||
)
|
||||
kjdk_current = winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
jdk_key_path + "\\" + version,
|
||||
)
|
||||
kjdk_current_values = dict( # pylint: disable=consider-using-dict-comprehension
|
||||
[
|
||||
winreg.EnumValue(kjdk_current, i)[:2]
|
||||
for i in range(winreg.QueryInfoKey(kjdk_current)[1])
|
||||
]
|
||||
)
|
||||
java_paths.append(kjdk_current_values["JavaHome"])
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
continue
|
||||
raise
|
||||
return java_paths
|
||||
|
||||
# If we get here we're linux so we will use 'update-alternatives'
|
||||
# (If distro does not have update-alternatives then manual input.)
|
||||
try:
|
||||
paths = subprocess.check_output(
|
||||
["/usr/bin/update-alternatives", "--list", "java"], encoding="utf8"
|
||||
)
|
||||
|
||||
if re.match("^(/[^/ ]*)+/?$", paths):
|
||||
return paths.split("\n")
|
||||
|
||||
except Exception as e:
|
||||
print("Java Detect Error: ", e)
|
||||
logger.error(f"Java Detect Error: {e}")
|
||||
|
||||
@staticmethod
|
||||
def float_to_string(gbs: float):
|
||||
s = str(float(gbs) * 1000).rstrip("0").rstrip(".")
|
||||
@ -443,53 +498,6 @@ class Helpers:
|
||||
return ctypes.windll.shell32.IsUserAnAdmin() == 1
|
||||
return os.geteuid() == 0
|
||||
|
||||
@staticmethod
|
||||
def unzip_file(zip_path):
|
||||
new_dir_list = zip_path.split("/")
|
||||
new_dir = ""
|
||||
for i in range(len(new_dir_list) - 1):
|
||||
if i == 0:
|
||||
new_dir += new_dir_list[i]
|
||||
else:
|
||||
new_dir += "/" + new_dir_list[i]
|
||||
|
||||
if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path):
|
||||
Helpers.ensure_dir_exists(new_dir)
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
for i in enumerate(zip_ref.filelist):
|
||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
|
||||
i
|
||||
].filename.endswith("/"):
|
||||
break
|
||||
|
||||
full_root_path = temp_dir
|
||||
|
||||
for item in os.listdir(full_root_path):
|
||||
if os.path.isdir(os.path.join(full_root_path, item)):
|
||||
try:
|
||||
FileHelpers.move_dir(
|
||||
os.path.join(full_root_path, item),
|
||||
os.path.join(new_dir, item),
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
||||
else:
|
||||
try:
|
||||
FileHelpers.move_file(
|
||||
os.path.join(full_root_path, item),
|
||||
os.path.join(new_dir, item),
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
||||
except Exception as ex:
|
||||
Console.error(ex)
|
||||
else:
|
||||
return "false"
|
||||
return
|
||||
|
||||
def ensure_logging_setup(self):
|
||||
log_file = os.path.join(os.path.curdir, "logs", "commander.log")
|
||||
session_log_file = os.path.join(os.path.curdir, "logs", "session.log")
|
||||
@ -837,7 +845,7 @@ class Helpers:
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
elif str(item) != "crafty.sqlite":
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||
unsorted_files, key=str.casefold
|
||||
@ -868,13 +876,14 @@ class Helpers:
|
||||
|
||||
@staticmethod
|
||||
def generate_dir(folder, output=""):
|
||||
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
elif str(item) != "crafty.sqlite":
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||
unsorted_files, key=str.casefold
|
||||
@ -991,14 +1000,6 @@ class Helpers:
|
||||
[parent_path, child_path]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def copy_files(source, dest):
|
||||
if os.path.isfile(source):
|
||||
FileHelpers.copy_file(source, dest)
|
||||
logger.info("Copying jar %s to %s", source, dest)
|
||||
else:
|
||||
logger.info("Source jar does not exist.")
|
||||
|
||||
@staticmethod
|
||||
def download_file(executable_url, jar_path):
|
||||
try:
|
||||
|
@ -34,8 +34,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Controller:
|
||||
def __init__(self, database, helper):
|
||||
def __init__(self, database, helper, file_helper):
|
||||
self.helper: Helpers = helper
|
||||
self.file_helper: FileHelpers = file_helper
|
||||
self.server_jars: ServerJars = ServerJars(helper)
|
||||
self.users_helper: HelperUsers = HelperUsers(database, self.helper)
|
||||
self.roles_helper: HelperRoles = HelperRoles(database)
|
||||
@ -53,7 +54,7 @@ class Controller:
|
||||
)
|
||||
self.server_perms: ServerPermsController = ServerPermsController()
|
||||
self.servers: ServersController = ServersController(
|
||||
self.helper, self.servers_helper, self.management_helper
|
||||
self.helper, self.servers_helper, self.management_helper, self.file_helper
|
||||
)
|
||||
self.users: UsersController = UsersController(
|
||||
self.helper, self.users_helper, self.authentication
|
||||
|
@ -17,8 +17,6 @@ class DatabaseBuilder:
|
||||
logger.info("Fresh Install Detected - Creating Default Settings")
|
||||
Console.info("Fresh Install Detected - Creating Default Settings")
|
||||
default_data = self.helper.find_default_password()
|
||||
# Reset this value if the DB has been dumped
|
||||
self.helper.set_setting("apikey_secret", "random")
|
||||
|
||||
username = default_data.get("username", "admin")
|
||||
password = default_data.get("password", "crafty")
|
||||
|
@ -9,7 +9,6 @@ import threading
|
||||
import logging.config
|
||||
import subprocess
|
||||
import html
|
||||
import tempfile
|
||||
|
||||
# TZLocal is set as a hidden import on win pipeline
|
||||
from tzlocal import get_localzone
|
||||
@ -102,12 +101,14 @@ class ServerOutBuf:
|
||||
class ServerInstance:
|
||||
server_object: Servers
|
||||
helper: Helpers
|
||||
file_helper: FileHelpers
|
||||
management_helper: HelpersManagement
|
||||
stats: Stats
|
||||
stats_helper: HelperServerStats
|
||||
|
||||
def __init__(self, server_id, helper, management_helper, stats):
|
||||
def __init__(self, server_id, helper, management_helper, stats, file_helper):
|
||||
self.helper = helper
|
||||
self.file_helper = file_helper
|
||||
self.management_helper = management_helper
|
||||
# holders for our process
|
||||
self.process = None
|
||||
@ -126,6 +127,7 @@ class ServerInstance:
|
||||
self.stats = stats
|
||||
self.server_object = HelperServers.get_server_obj(self.server_id)
|
||||
self.stats_helper = HelperServerStats(self.server_id)
|
||||
self.last_backup_failed = False
|
||||
try:
|
||||
tz = get_localzone()
|
||||
except ZoneInfoNotFoundError:
|
||||
@ -239,6 +241,11 @@ class ServerInstance:
|
||||
"Detected nebulous java in start command. "
|
||||
"Replacing with full java path."
|
||||
)
|
||||
# Checks for Oracle Java. Only Oracle Java's helper will cause a re-exec.
|
||||
if "/Oracle/Java/" in str(shutil.which("java")):
|
||||
logger.info(
|
||||
"Oracle Java detected. Changing start command to avoid re-exec."
|
||||
)
|
||||
which_java_raw = self.helper.which_java()
|
||||
java_path = which_java_raw + "\\bin\\java"
|
||||
if str(which_java_raw) != str(self.helper.get_servers_root_dir) or str(
|
||||
@ -840,6 +847,7 @@ class ServerInstance:
|
||||
"backup_reload",
|
||||
{"percent": 0, "total_files": 0},
|
||||
)
|
||||
was_server_running = None
|
||||
logger.info(f"Starting server {self.name} (ID {self.server_id}) backup")
|
||||
server_users = PermissionsServers.get_server_user_list(self.server_id)
|
||||
for user in server_users:
|
||||
@ -852,6 +860,15 @@ class ServerInstance:
|
||||
)
|
||||
time.sleep(3)
|
||||
conf = HelpersManagement.get_backup_config(self.server_id)
|
||||
if conf["shutdown"]:
|
||||
logger.info(
|
||||
"Found shutdown preference. Delaying"
|
||||
+ "backup start. Shutting down server."
|
||||
)
|
||||
if self.check_running():
|
||||
self.stop_server()
|
||||
was_server_running = True
|
||||
|
||||
self.helper.ensure_dir_exists(self.settings["backup_path"])
|
||||
try:
|
||||
backup_filename = (
|
||||
@ -863,62 +880,27 @@ class ServerInstance:
|
||||
f" (ID#{self.server_id}, path={self.server_path}) "
|
||||
f"at '{backup_filename}'"
|
||||
)
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
self.server_scheduler.add_job(
|
||||
self.backup_status,
|
||||
"interval",
|
||||
seconds=1,
|
||||
id="backup_" + str(self.server_id),
|
||||
args=[temp_dir + "/", backup_filename + ".zip"],
|
||||
)
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
try:
|
||||
FileHelpers.copy_dir(self.server_path, temp_dir, dirs_exist_ok=True)
|
||||
except shutil.Error as e:
|
||||
logger.error(f"Failed to fully complete backup due to shutil error {e}")
|
||||
excluded_dirs = HelpersManagement.get_excluded_backup_dirs(self.server_id)
|
||||
server_dir = Helpers.get_os_understandable_path(self.settings["path"])
|
||||
|
||||
for my_dir in excluded_dirs:
|
||||
# Take the full path of the excluded dir and replace the
|
||||
# server path with the temp path, this is so that we're
|
||||
# only deleting excluded dirs from the temp path
|
||||
# and not the server path
|
||||
excluded_dir = Helpers.get_os_understandable_path(my_dir).replace(
|
||||
server_dir, Helpers.get_os_understandable_path(temp_dir)
|
||||
)
|
||||
# Next, check to see if it is a directory
|
||||
if os.path.isdir(excluded_dir):
|
||||
# If it is a directory,
|
||||
# recursively delete the entire directory from the backup
|
||||
try:
|
||||
FileHelpers.del_dirs(excluded_dir)
|
||||
except FileNotFoundError:
|
||||
Console.error(
|
||||
f"Excluded dir {excluded_dir} not found. Moving on..."
|
||||
)
|
||||
else:
|
||||
# If not, just remove the file
|
||||
try:
|
||||
os.remove(excluded_dir)
|
||||
except:
|
||||
Console.error(
|
||||
f"Excluded dir {excluded_dir} not found. Moving on..."
|
||||
)
|
||||
if conf["compress"]:
|
||||
logger.debug(
|
||||
"Found compress backup to be true. Calling compressed archive"
|
||||
)
|
||||
FileHelpers.make_compressed_archive(
|
||||
Helpers.get_os_understandable_path(backup_filename), temp_dir
|
||||
self.file_helper.make_compressed_backup(
|
||||
Helpers.get_os_understandable_path(backup_filename),
|
||||
server_dir,
|
||||
excluded_dirs,
|
||||
self.server_id,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Found compress backup to be false. Calling NON-compressed archive"
|
||||
)
|
||||
FileHelpers.make_archive(
|
||||
Helpers.get_os_understandable_path(backup_filename), temp_dir
|
||||
self.file_helper.make_backup(
|
||||
Helpers.get_os_understandable_path(backup_filename),
|
||||
server_dir,
|
||||
excluded_dirs,
|
||||
self.server_id,
|
||||
)
|
||||
|
||||
while (
|
||||
@ -933,7 +915,6 @@ class ServerInstance:
|
||||
|
||||
self.is_backingup = False
|
||||
logger.info(f"Backup of server: {self.name} completed")
|
||||
self.server_scheduler.remove_job("backup_" + str(self.server_id))
|
||||
results = {"percent": 100, "total_files": 0, "current_file": 0}
|
||||
if len(self.helper.websocket_helper.clients) > 0:
|
||||
self.helper.websocket_helper.broadcast_page_params(
|
||||
@ -953,12 +934,17 @@ class ServerInstance:
|
||||
HelperUsers.get_user_lang_by_id(user),
|
||||
).format(self.name),
|
||||
)
|
||||
if was_server_running:
|
||||
logger.info(
|
||||
"Backup complete. User had shutdown preference. Starting server."
|
||||
)
|
||||
self.start_server(HelperUsers.get_user_id_by_name("system"))
|
||||
time.sleep(3)
|
||||
self.last_backup_failed = False
|
||||
except:
|
||||
logger.exception(
|
||||
f"Failed to create backup of server {self.name} (ID {self.server_id})"
|
||||
)
|
||||
self.server_scheduler.remove_job("backup_" + str(self.server_id))
|
||||
results = {"percent": 100, "total_files": 0, "current_file": 0}
|
||||
if len(self.helper.websocket_helper.clients) > 0:
|
||||
self.helper.websocket_helper.broadcast_page_params(
|
||||
@ -968,8 +954,12 @@ class ServerInstance:
|
||||
results,
|
||||
)
|
||||
self.is_backingup = False
|
||||
finally:
|
||||
FileHelpers.del_dirs(temp_dir)
|
||||
if was_server_running:
|
||||
logger.info(
|
||||
"Backup complete. User had shutdown preference. Starting server."
|
||||
)
|
||||
self.start_server(HelperUsers.get_user_id_by_name("system"))
|
||||
self.last_backup_failed = True
|
||||
|
||||
def backup_status(self, source_path, dest_path):
|
||||
results = Helpers.calc_percent(source_path, dest_path)
|
||||
@ -982,6 +972,9 @@ class ServerInstance:
|
||||
results,
|
||||
)
|
||||
|
||||
def last_backup_status(self):
|
||||
return self.last_backup_failed
|
||||
|
||||
def send_backup_status(self):
|
||||
try:
|
||||
return self.backup_stats
|
||||
@ -1087,7 +1080,7 @@ class ServerInstance:
|
||||
)
|
||||
|
||||
# copies to backup dir
|
||||
Helpers.copy_files(current_executable, backup_executable)
|
||||
FileHelpers.copy_file(current_executable, backup_executable)
|
||||
|
||||
# boolean returns true for false for success
|
||||
downloaded = Helpers.download_file(
|
||||
@ -1249,7 +1242,7 @@ class ServerInstance:
|
||||
server_path = server["path"]
|
||||
|
||||
# process stats
|
||||
p_stats = Stats._try_get_process_stats(self.process)
|
||||
p_stats = Stats._try_get_process_stats(self.process, self.check_running())
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
internal_ip = server["server_ip"]
|
||||
@ -1382,7 +1375,7 @@ class ServerInstance:
|
||||
server_path = server_dt["path"]
|
||||
|
||||
# process stats
|
||||
p_stats = Stats._try_get_process_stats(self.process)
|
||||
p_stats = Stats._try_get_process_stats(self.process, self.check_running())
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
# internal_ip = server['server_ip']
|
||||
|
@ -220,7 +220,7 @@ class FileHandler(BaseHandler):
|
||||
path = Helpers.get_os_understandable_path(self.get_argument("path", None))
|
||||
if Helpers.is_os_windows():
|
||||
path = Helpers.wtol_path(path)
|
||||
Helpers.unzip_file(path)
|
||||
FileHelpers.unzip_file(path)
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
|
||||
return
|
||||
|
||||
|
@ -6,6 +6,7 @@ import typing as t
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
import shlex
|
||||
import bleach
|
||||
import libgravatar
|
||||
import requests
|
||||
@ -183,6 +184,7 @@ class PanelHandler(BaseHandler):
|
||||
logger.debug(f'User {exec_user["user_id"]} does not have permission')
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return None
|
||||
return server_id
|
||||
|
||||
# Server fetching, spawned asynchronously
|
||||
# TODO: Make the related front-end elements update with AJAX
|
||||
@ -496,6 +498,10 @@ class PanelHandler(BaseHandler):
|
||||
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()
|
||||
server_obj = None
|
||||
|
||||
valid_subpages = [
|
||||
"term",
|
||||
"logs",
|
||||
@ -626,6 +632,18 @@ class PanelHandler(BaseHandler):
|
||||
"/panel/error?error=Unauthorized access Server Config"
|
||||
)
|
||||
return
|
||||
page_data["java_versions"] = Helpers.find_java_installs()
|
||||
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
|
||||
page_java = []
|
||||
page_data["java_versions"].append("java")
|
||||
for version in page_data["java_versions"]:
|
||||
if os.name == "nt":
|
||||
page_java.append(version)
|
||||
else:
|
||||
if len(version) > 0:
|
||||
page_java.append(version)
|
||||
|
||||
page_data["java_versions"] = page_java
|
||||
|
||||
if subpage == "files":
|
||||
if (
|
||||
@ -1057,6 +1075,11 @@ class PanelHandler(BaseHandler):
|
||||
if user_id is None:
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
return
|
||||
if int(user_id) != exec_user["user_id"] and not exec_user["superuser"]:
|
||||
self.redirect(
|
||||
"/panel/error?error=You are not authorized to view this page."
|
||||
)
|
||||
return
|
||||
|
||||
template = "panel/panel_edit_user_apikeys.html"
|
||||
|
||||
@ -1221,6 +1244,9 @@ class PanelHandler(BaseHandler):
|
||||
self.download_file(name, file)
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
|
||||
|
||||
elif page == "wiki":
|
||||
template = "panel/wiki.html"
|
||||
|
||||
elif page == "download_support_package":
|
||||
temp_zip_storage = exec_user["support_logs"]
|
||||
|
||||
@ -1333,6 +1359,8 @@ class PanelHandler(BaseHandler):
|
||||
if Helpers.is_os_windows():
|
||||
log_path.replace(" ", "^ ")
|
||||
log_path = Helpers.wtol_path(log_path)
|
||||
if not self.helper.validate_traversal(server_obj.path, log_path):
|
||||
log_path = ""
|
||||
executable = self.get_argument("executable", None)
|
||||
execution_command = self.get_argument("execution_command", None)
|
||||
server_ip = self.get_argument("server_ip", None)
|
||||
@ -1346,11 +1374,46 @@ class PanelHandler(BaseHandler):
|
||||
auto_start = int(float(self.get_argument("auto_start", "0")))
|
||||
crash_detection = int(float(self.get_argument("crash_detection", "0")))
|
||||
logs_delete_after = int(float(self.get_argument("logs_delete_after", "0")))
|
||||
java_selection = self.get_argument("java_selection", None)
|
||||
# subpage = self.get_argument('subpage', None)
|
||||
|
||||
server_id = self.check_server_id()
|
||||
if server_id is None:
|
||||
return
|
||||
if java_selection:
|
||||
try:
|
||||
execution_list = shlex.split(execution_command)
|
||||
except ValueError:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid execution command. Java path"
|
||||
" must be surrounded by quotes."
|
||||
" (Are you missing a closing quote?)"
|
||||
)
|
||||
if not any(
|
||||
java_selection in path for path in Helpers.find_java_installs()
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Attack attempted."
|
||||
+ " A copy of this report is being sent to server owner."
|
||||
)
|
||||
self.controller.management.add_to_audit_log_raw(
|
||||
exec_user["username"],
|
||||
exec_user["user_id"],
|
||||
server_id,
|
||||
f"Attempted to send bad java path for {server_id}."
|
||||
+ " Possible attack. Act accordingly.",
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
if java_selection != "java":
|
||||
if self.helper.is_os_windows():
|
||||
execution_list[0] = '"' + java_selection + '/bin/java"'
|
||||
else:
|
||||
execution_list[0] = '"' + java_selection + '"'
|
||||
else:
|
||||
execution_list[0] = "java"
|
||||
execution_command = ""
|
||||
for item in execution_list:
|
||||
execution_command += item + " "
|
||||
|
||||
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
|
||||
stale_executable = server_obj.executable
|
||||
@ -1380,7 +1443,7 @@ class PanelHandler(BaseHandler):
|
||||
server_obj.path = server_obj.path
|
||||
server_obj.log_path = server_obj.log_path
|
||||
server_obj.executable = server_obj.executable
|
||||
server_obj.execution_command = server_obj.execution_command
|
||||
server_obj.execution_command = execution_command
|
||||
server_obj.server_ip = server_obj.server_ip
|
||||
server_obj.server_port = server_obj.server_port
|
||||
server_obj.executable_update_url = server_obj.executable_update_url
|
||||
@ -1424,6 +1487,7 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
compress = self.get_argument("compress", False)
|
||||
shutdown = self.get_argument("shutdown", False)
|
||||
check_changed = self.get_argument("changed")
|
||||
if str(check_changed) == str(1):
|
||||
checked = self.get_body_arguments("root_path")
|
||||
@ -1446,6 +1510,7 @@ class PanelHandler(BaseHandler):
|
||||
max_backups=max_backups,
|
||||
excluded_dirs=checked,
|
||||
compress=bool(compress),
|
||||
shutdown=bool(shutdown),
|
||||
)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
@ -1892,6 +1957,13 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
return
|
||||
|
||||
if str(user_id) != str(exec_user["user_id"]) and not exec_user["superuser"]:
|
||||
self.redirect(
|
||||
"/panel/error?error=You do not have access to change"
|
||||
+ "this user's api key."
|
||||
)
|
||||
return
|
||||
|
||||
crafty_permissions_mask = self.get_perms()
|
||||
server_permissions_mask = self.get_perms_server()
|
||||
|
||||
@ -1925,6 +1997,15 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/error?error=Invalid Key ID")
|
||||
return
|
||||
|
||||
if (
|
||||
str(key.user_id) != str(exec_user["user_id"])
|
||||
and not exec_user["superuser"]
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=You are not authorized to access this key."
|
||||
)
|
||||
return
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Generated a new API token for the key {key.name} "
|
||||
@ -2141,6 +2222,15 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/error?error=Invalid Key ID")
|
||||
return
|
||||
|
||||
key_obj = self.controller.users.get_user_api_key(key_id)
|
||||
|
||||
if key_obj.user_id != exec_user["user_id"] and not exec_user["superuser"]:
|
||||
self.redirect(
|
||||
"/panel/error?error=You do not have access to change"
|
||||
+ "this user's api key."
|
||||
)
|
||||
return
|
||||
|
||||
self.controller.users.delete_user_api_key(key_id)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
@ -2150,7 +2240,8 @@ class PanelHandler(BaseHandler):
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip(),
|
||||
)
|
||||
self.redirect("/panel/panel_config")
|
||||
self.finish()
|
||||
self.redirect(f"/panel/edit_user_apikeys?id={key_obj.user_id}")
|
||||
else:
|
||||
self.set_status(404)
|
||||
self.render(
|
||||
|
@ -17,6 +17,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServerHandler(BaseHandler):
|
||||
def get_user_roles(self):
|
||||
user_roles = {}
|
||||
for user_id in self.controller.users.get_all_user_ids():
|
||||
user_roles_list = self.controller.users.get_user_roles_names(user_id)
|
||||
# user_servers =
|
||||
# self.controller.servers.get_authorized_servers(user.user_id)
|
||||
user_roles[user_id] = user_roles_list
|
||||
return user_roles
|
||||
|
||||
@tornado.web.authenticated
|
||||
def get(self, page):
|
||||
(
|
||||
@ -271,11 +280,19 @@ class ServerHandler(BaseHandler):
|
||||
)
|
||||
|
||||
if page == "step1":
|
||||
if not superuser and not self.controller.crafty_perms.can_create_server(
|
||||
exec_user["user_id"]
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Unauthorized access: "
|
||||
"not a server creator or server limit reached"
|
||||
)
|
||||
return
|
||||
|
||||
if not superuser:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
else:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
user_roles = self.get_user_roles()
|
||||
server = bleach.clean(self.get_argument("server", ""))
|
||||
server_name = bleach.clean(self.get_argument("server_name", ""))
|
||||
min_mem = bleach.clean(self.get_argument("min_memory", ""))
|
||||
@ -396,6 +413,14 @@ class ServerHandler(BaseHandler):
|
||||
self.redirect("/panel/dashboard")
|
||||
|
||||
if page == "bedrock_step1":
|
||||
if not superuser and not self.controller.crafty_perms.can_create_server(
|
||||
exec_user["user_id"]
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Unauthorized access: "
|
||||
"not a server creator or server limit reached"
|
||||
)
|
||||
return
|
||||
if not superuser:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
else:
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": 2,
|
||||
"sub": 4,
|
||||
"meta": "beta"
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
}
|
||||
|
||||
|
||||
.sidebar > .nav > .nav-item:not(.nav-profile) > .nav-link:before {
|
||||
.sidebar>.nav>.nav-item:not(.nav-profile)>.nav-link:before {
|
||||
content: none;
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
@ -21,7 +21,7 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar > .nav > .nav-item:not(.nav-profile) > .nav-link:before {
|
||||
.sidebar>.nav>.nav-item:not(.nav-profile)>.nav-link:before {
|
||||
content: none;
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
@ -33,43 +33,48 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar > .nav .nav-item .nav-link, .collapsed{
|
||||
.sidebar>.nav .nav-item .nav-link,
|
||||
.collapsed {
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.mc-log-time{
|
||||
color:#19d895;
|
||||
.mc-log-time {
|
||||
color: #19d895;
|
||||
}
|
||||
|
||||
.mc-log-info{
|
||||
color:#8862e0;
|
||||
.mc-log-info {
|
||||
color: #8862e0;
|
||||
}
|
||||
|
||||
.mc-log-warn{
|
||||
color:#ffaf00;
|
||||
.mc-log-warn {
|
||||
color: #ffaf00;
|
||||
}
|
||||
|
||||
.mc-log-error{
|
||||
color:#af463f;
|
||||
.mc-log-error {
|
||||
color: #af463f;
|
||||
}
|
||||
|
||||
.mc-log-fatal{
|
||||
color:#da0f00;
|
||||
.mc-log-fatal {
|
||||
color: #da0f00;
|
||||
}
|
||||
|
||||
.mc-log-keyword{
|
||||
color:#2196f3;
|
||||
.mc-log-keyword {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.scrollable-element {
|
||||
scrollbar-color: red yellow;
|
||||
}
|
||||
|
||||
.term-nav-item {
|
||||
padding: 1%;
|
||||
}
|
||||
|
||||
/* Fix body scrollbar color */
|
||||
body { background-color: var(--dark) !important; /* Firefox */ }
|
||||
body {
|
||||
background-color: var(--dark) !important;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
/* Webkit */
|
||||
/* Didn't really work out
|
||||
@ -81,11 +86,20 @@ body { background-color: var(--dark) !important; /* Firefox */ }
|
||||
::-webkit-scrollbar-track { background-color: #202538; }
|
||||
::-webkit-scrollbar-corner { background-color: #202538; }*/
|
||||
|
||||
.actions_serverlist > a > i {
|
||||
.actions_serverlist>a>i {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.actions_serveritem {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.corner {
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.accordion .card {
|
||||
margin-bottom: 0px;
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
<!-- partial -->
|
||||
<div class="container-fluid page-body-wrapper">
|
||||
<div class="container-fluid page-body-wrapper">
|
||||
<!-- partial:partials/_sidebar.html -->
|
||||
<style>
|
||||
@media screen and (max-width: 991px) {
|
||||
.sidebar-offcanvas {
|
||||
-webkit-transition: all 0.25s cubic-bezier(.22,.61,.36,1);
|
||||
transition: all 0.25s cubic-bezier(.22,.61,.36,1);
|
||||
box-shadow: 0px 8px 17px 2px rgba(0,0,0,0.14) , 0px 3px 14px 2px rgba(0,0,0,0.12) , 0px 5px 5px -3px rgba(0,0,0,0.2);
|
||||
-webkit-transition: all 0.25s cubic-bezier(.22, .61, .36, 1);
|
||||
transition: all 0.25s cubic-bezier(.22, .61, .36, 1);
|
||||
box-shadow: 0px 8px 17px 2px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12), 0px 5px 5px -3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function debounce(func, wait, immediate) {
|
||||
var timeout;
|
||||
return function() {
|
||||
return function () {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
var later = function () {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
@ -33,7 +33,7 @@
|
||||
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
return vw >= 992;
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$(document).ready(function () {
|
||||
sidebarResizeHandler(null);
|
||||
$(window).on(
|
||||
'resize',
|
||||
@ -66,7 +66,8 @@
|
||||
<nav class="sidebar sidebar-offcanvas" id="sidebar">
|
||||
<ul class="nav">
|
||||
|
||||
<li class="nav-item nav-category" style="margin-top:10px;">{{ translate('sidebar', 'navigation', data['lang']) }}</li>
|
||||
<li class="nav-item nav-category" style="margin-top:10px;">{{ translate('sidebar', 'navigation', data['lang']) }}
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/dashboard">
|
||||
@ -76,7 +77,8 @@
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="collapse" href="#page-layouts" aria-expanded="false" aria-controls="page-layouts">
|
||||
<a class="nav-link" data-toggle="collapse" href="#page-layouts" aria-expanded="false"
|
||||
aria-controls="page-layouts">
|
||||
<i class="fas fa-server"></i>
|
||||
<span class="menu-title">{{ translate('sidebar', 'servers', data['lang']) }}</span>
|
||||
<i class="menu-arrow"></i>
|
||||
@ -85,12 +87,14 @@
|
||||
<ul class="nav flex-column sub-menu">
|
||||
{% if data['crafty_permissions']['Server_Creation'] in data['user_crafty_permissions'] %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{ translate('sidebar', 'newServer', data['lang']) }}</a>
|
||||
<a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{ translate('sidebar',
|
||||
'newServer', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% for s in data['menu_servers'] %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{s['server_id']}}"><i class="fas fa-server"></i> {{s['server_name']}}</a>
|
||||
<a class="nav-link" href="/panel/server_detail?id={{s['server_id']}}"><i class="fas fa-server"></i>
|
||||
{{s['server_name']}}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
|
||||
@ -105,6 +109,13 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/wiki">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<span class="menu-title">Wiki</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://discord.gg/9VJPhCE" target="_blank">
|
||||
<i class="fab fa-discord"></i>
|
||||
|
@ -25,15 +25,15 @@
|
||||
<!-- Page Title Header Ends-->
|
||||
{% if data['first_log'] %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$(document).ready(function () {
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: 'Your Feedback Is Appreciated...',
|
||||
message: '<p>We will only request this information from the admin user once... 😊</p>'
|
||||
+'<p><strong>All data collected is completely anonymous</strong> and is only used to improve Crafty 4.0 and allow us to more accurately report the number of Crafty 4 users.</p><iframe width="640px" height="480px"'
|
||||
+'src="https://forms.office.com/Pages/ResponsePage.aspx?id=LwLajNkpXU2CKc95G1oO3MN0Hu3oEUNLr-EtLx31TS5UNUNVQlFNVUVYMEc'
|
||||
+'1V1BKS0FQUUlERUtWQy4u&embed=true" frameborder="0" marginwidth="0" marginheight="0" style="border: none; max-width:100%;'
|
||||
+' max-height:100vh" allowfullscreen webkitallowfullscreen mozallowfullscreen msallowfullscreen> </iframe>',
|
||||
+ '<p><strong>All data collected is completely anonymous</strong> and is only used to improve Crafty 4.0 and allow us to more accurately report the number of Crafty 4 users.</p><iframe width="640px" height="480px"'
|
||||
+ 'src="https://forms.office.com/Pages/ResponsePage.aspx?id=LwLajNkpXU2CKc95G1oO3MN0Hu3oEUNLr-EtLx31TS5UNUNVQlFNVUVYMEc'
|
||||
+ '1V1BKS0FQUUlERUtWQy4u&embed=true" frameborder="0" marginwidth="0" marginheight="0" style="border: none; max-width:100%;'
|
||||
+ ' max-height:100vh" allowfullscreen webkitallowfullscreen mozallowfullscreen msallowfullscreen> </iframe>',
|
||||
buttons: {
|
||||
ok: {
|
||||
label: 'Skip Survey/Done',
|
||||
@ -125,7 +125,6 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="table-responsive">
|
||||
{% if len(data['servers']) == 0%}
|
||||
<div style="text-align: center; color: grey;">
|
||||
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
|
||||
@ -137,7 +136,8 @@
|
||||
{% end %}
|
||||
{% if len(data['servers']) > 0 %}
|
||||
<!-- View for Large screen -->
|
||||
<table id="servers_table" class="table table-hover d-none d-sm-table">
|
||||
<div class="table-responsive d-none d-sm-block">
|
||||
<table id="servers_table" class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded" id="first" draggable="false">
|
||||
<th draggable="false">{{ translate('dashboard', 'server', data['lang']) }}</th>
|
||||
@ -285,71 +285,30 @@
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- View for Small screen -->
|
||||
<table id="servers_table" class="table table-hover d-table d-sm-none">
|
||||
<thead>
|
||||
<tr class="rounded" id="first" draggable="false">
|
||||
<th scope="col" draggable="false">{{ translate('dashboard', 'server', data['lang']) }}</th>
|
||||
<th scope="col" draggable="false">{{ translate('dashboard', 'actions', data['lang']) }}</th>
|
||||
<th scope="col" draggable="false">{{ translate('dashboard', 'status', data['lang']) }}</th>
|
||||
<th scope="col" draggable="false"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</div>
|
||||
{% end %}
|
||||
{% if len(data['servers']) > 0 %}
|
||||
<!-- Try with Accordion -->
|
||||
<div class="d-sm-none d-block">
|
||||
<div class="accordion" id="accordionServers">
|
||||
{% for server in data['servers'] %}
|
||||
<tr id="{{server['server_data']['server_id']}}" draggable="false">
|
||||
<td scope="row"><i class="fas fa-server"></i>
|
||||
<a draggable="false" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
|
||||
{{ server['server_data']['server_name'] }}
|
||||
<div class="card">
|
||||
<div class="card-header" id="heading-{{server['server_data']['server_id']}}">
|
||||
<h2 class="mb-0 container overflow-hidden">
|
||||
<div class="row">
|
||||
<div class="col-10 col-lg-3 mx-0 px-0">
|
||||
<a class="btn btn-link d-flex justify-content-start" type="button" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
|
||||
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}
|
||||
</a>
|
||||
</td>
|
||||
<td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
|
||||
{% 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']) }}">
|
||||
<i class="fas fa-stop"></i>
|
||||
</a>
|
||||
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip"
|
||||
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
|
||||
<i class="fas fa-sync"></i>
|
||||
</a>
|
||||
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
|
||||
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
|
||||
<i class="fas fa-skull"></i>
|
||||
</a>
|
||||
|
||||
{% elif server['stats']['updating']%}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating',
|
||||
data['lang']) }}</i></a>
|
||||
{% elif server['stats']['waiting_start']%}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="" title="{{
|
||||
translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting',
|
||||
data['lang']) }}</i></a>
|
||||
{% elif server['stats']['downloading']%}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i>
|
||||
{{ translate('serverTerm', 'downloading', data['lang']) }}</a>
|
||||
{% else %}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip"
|
||||
title="{{ translate('dashboard', 'start' , data['lang']) }}">
|
||||
<i class="fas fa-play"></i>
|
||||
</a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="clone_button" data-toggle="tooltip"
|
||||
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
|
||||
<i class="fas fa-clone"></i>
|
||||
</a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
|
||||
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
|
||||
<i class="fas fa-skull"></i>
|
||||
</a>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</td>
|
||||
<td draggable="false" id="m_server_running_status_{{server['server_data']['server_id']}}">
|
||||
</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']}}">
|
||||
<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">
|
||||
{% if server['stats']['running'] %}
|
||||
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
|
||||
data['lang']) }}</span>
|
||||
@ -361,16 +320,86 @@
|
||||
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
|
||||
data['lang']) }}</span>
|
||||
{% end %}
|
||||
</td>
|
||||
<td>
|
||||
<span data-toggle="collapse" data-target="#details_{{server['server_data']['server_id']}}"
|
||||
aria-expanded="false" aria-controls="details_{{server['server_data']['server_id']}}"><i
|
||||
class="fas fa-chevron-down"></i></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="details_{{server['server_data']['server_id']}}" class="collapse" draggable="false">
|
||||
<td colspan="4">
|
||||
<div class="collapse" id="details_{{server['server_data']['server_id']}}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-8 col-lg-3 mx-0 px-0">
|
||||
<div id="controls{{server['server_data']['server_id']}}" class="container overflow-hidden">
|
||||
{% if server['user_command_permission'] %}
|
||||
{% if server['stats']['running'] %}
|
||||
<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']) }}">
|
||||
<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']) }}">
|
||||
<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']) }}">
|
||||
<i class="fas fa-skull"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% elif server['stats']['updating']%}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<div class="row">
|
||||
<div class="col-12 px-0">
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link">{{ translate('serverTerm', 'updating',
|
||||
data['lang']) }}</i></a>
|
||||
</div>
|
||||
</div>
|
||||
{% elif server['stats']['waiting_start']%}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<div class="row">
|
||||
<div class="col-12 px-0">
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link" title="{{
|
||||
translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting',
|
||||
data['lang']) }}</i></a>
|
||||
</div>
|
||||
</div>
|
||||
{% elif server['stats']['downloading']%}
|
||||
<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>
|
||||
{{ translate('serverTerm', 'downloading', data['lang']) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<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']) }}">
|
||||
<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']) }}">
|
||||
<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']) }}">
|
||||
<i class="fas fa-skull"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse" 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>
|
||||
@ -447,14 +476,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Crafty Commander</title>
|
||||
<title>Crafty Controller</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
|
@ -19,11 +19,18 @@
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Backup'] in data['user_permissions'] %}
|
||||
{% if data['backup_failed'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a style="color: red !important;" class="nav-link {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
|
||||
<i class="fas fa-save"></i>{{ translate('serverDetails', 'backup', data['lang']) }} <i class="fas fa-exclamation-triangle"> </i></a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
|
||||
<i class="fas fa-save"></i>{{ translate('serverDetails', 'backup', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% if data['permissions']['Files'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'files' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">
|
||||
|
@ -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>
|
||||
@ -51,55 +52,85 @@
|
||||
|
||||
{% if data['backing_up'] %}
|
||||
<div class="progress" style="height: 15px;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar" role="progressbar" style="width:{{data['backup_stats']['percent']}}%;" aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{ data['backup_stats']['percent'] }}%</div>
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
|
||||
role="progressbar" style="width:{{data['backup_stats']['percent']}}%;"
|
||||
aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{
|
||||
data['backup_stats']['percent'] }}%</div>
|
||||
</div>
|
||||
<p>Backing up <span id="total_files">{{data['backup_stats']['total_files']}}</span> Files</p>
|
||||
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
|
||||
id="total_files">{{data['server_stats']['world_size']}}</span></p>
|
||||
{% end %}
|
||||
|
||||
<br>
|
||||
{% if not data['backing_up'] %}
|
||||
<div id="backup_button" class="form-group">
|
||||
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow', data['lang']) }}</button>
|
||||
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow',
|
||||
data['lang']) }}</button>
|
||||
</div>
|
||||
{% end %}
|
||||
<div class="form-group">
|
||||
{% if data['super_user'] %}
|
||||
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="backup_path" id="backup_path" value="{{ data['server_stats']['server_id']['backup_path'] }}" placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}">
|
||||
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="text" class="form-control" name="backup_path" id="backup_path"
|
||||
value="{{ data['server_stats']['server_id']['backup_path'] }}"
|
||||
placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}">
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
|
||||
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="text" class="form-control" name="max_backups" id="max_backups"
|
||||
value="{{ data['backup_config']['max_backups'] }}"
|
||||
placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="compress" class="form-check-label ml-4 mb-4"></label>
|
||||
{% if data['backup_config']['compress'] %}
|
||||
<input type="checkbox" class="form-check-input" id="compress" name="compress"
|
||||
checked="" value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="compress" name="compress"
|
||||
<input type="checkbox" class="form-check-input" id="compress" name="compress" checked=""
|
||||
value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="compress" name="compress" value="True">{{
|
||||
translate('serverBackups', 'compress', data['lang']) }}
|
||||
{% end %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{ translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
|
||||
<br>
|
||||
<button class="btn btn-primary mr-2" id="root_files_button" data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{ translate('serverBackups', 'clickExclude', data['lang']) }}</button>
|
||||
<label for="shutdown" class="form-check-label ml-4 mb-4"></label>
|
||||
{% if data['backup_config']['shutdown'] %}
|
||||
<input type="checkbox" class="form-check-input" id="shutdown" name="shutdown" checked=""
|
||||
value="True">{{ translate('serverBackups', 'shutdown', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="shutdown" name="shutdown" value="True">{{
|
||||
translate('serverBackups', 'shutdown', data['lang']) }}
|
||||
{% end %}
|
||||
</div>
|
||||
<input type="number" class="form-control" name="changed" id="changed" value="0" style="visibility: hidden;"></input>
|
||||
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{
|
||||
translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
|
||||
<br>
|
||||
<button class="btn btn-primary mr-2" id="root_files_button"
|
||||
data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{
|
||||
translate('serverBackups', 'clickExclude', data['lang']) }}</button>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="changed" id="changed" value="0"
|
||||
style="visibility: hidden;"></input>
|
||||
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups', 'excludedChoose', data['lang']) }}</h5>
|
||||
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups',
|
||||
'excludedChoose', data['lang']) }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
|
||||
<div class="tree-ctx-item" id="main-tree-div" data-path=""
|
||||
style="overflow: scroll; max-height:75%;">
|
||||
<input type="checkbox" id="main-tree-input" name="root_path" value="" disabled>
|
||||
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
|
||||
<i class="far fa-folder"></i>
|
||||
@ -110,15 +141,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
|
||||
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button>
|
||||
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{
|
||||
translate('serverBackups', 'cancel', data['lang']) }}</button>
|
||||
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
|
||||
translate('serverWizard', 'save', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
|
||||
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang'])
|
||||
}}</button>
|
||||
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang'])
|
||||
}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -138,13 +173,15 @@
|
||||
{% for backup in data['backup_list'] %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary">
|
||||
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}"
|
||||
class="btn btn-primary">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'download', data['lang']) }}
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
<button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}" class="btn btn-danger del_button">
|
||||
<button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}"
|
||||
class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'delete', data['lang']) }}
|
||||
</button>
|
||||
@ -168,7 +205,8 @@
|
||||
<br>
|
||||
<br>
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups', data['lang']) }} <small class="text-muted ml-1"></small> </h4>
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups',
|
||||
data['lang']) }} <small class="text-muted ml-1"></small> </h4>
|
||||
</div>
|
||||
<br>
|
||||
<ul>
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<form class="forms-sample" method="post" action="/panel/server_detail">
|
||||
<form class="forms-sample" method="post" id="config_form" action="/panel/server_detail">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
@ -50,8 +50,8 @@
|
||||
placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{% if data['super_user'] %}
|
||||
<div class="form-group">
|
||||
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
|
||||
</label>
|
||||
@ -78,7 +78,24 @@
|
||||
value="{{ data['server_stats']['server_id']['executable'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
|
||||
</div>
|
||||
{% end %}
|
||||
{% if data['server_stats']['server_type'] == "minecraft-java" %}
|
||||
<div class="form-group">
|
||||
<label for="java_selection">{{ translate('serverConfig', 'javaVersion', data['lang']) }}
|
||||
<small class="text-muted ml-1">{{ translate('serverConfig', 'javaVersionDesc', data['lang']) }}</small>
|
||||
</label>
|
||||
<select class="form-select form-control form-control-lg select-css" id="java_selection"
|
||||
name="java_selection" form="config_form">
|
||||
<option value="">{{ translate('serverConfig',
|
||||
'javaNoChange', data['lang'])}}</option>
|
||||
{% for path in data['java_versions'] %}
|
||||
<option value="{{path}}">{{path}}</option>
|
||||
{% end %}
|
||||
</select>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
{% if data['super_user'] %}
|
||||
<div class="form-group">
|
||||
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
|
||||
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc',
|
||||
@ -86,8 +103,14 @@
|
||||
<input type="text" class="form-control" name="execution_command" id="execution_command"
|
||||
value="{{ data['server_stats']['server_id']['execution_command'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
|
||||
{% end %}
|
||||
</div>
|
||||
{% else %}
|
||||
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<span style="color: gray;">{{ data['server_stats']['server_id']['execution_command'] }}</span> 🔒
|
||||
</div>
|
||||
<br>
|
||||
{% end %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small
|
||||
|
50
app/frontend/templates/panel/wiki.html
Normal file
50
app/frontend/templates/panel/wiki.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Wiki{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">Wiki</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 grid-margin">
|
||||
<iframe src="https://wiki.craftycontrol.com" width=100% height=2200px title="crafty's wiki"></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
<style>
|
||||
.popover-body {
|
||||
color: white !important;
|
||||
;
|
||||
}
|
||||
|
||||
#desc_id {
|
||||
-ms-overflow-style: none;
|
||||
/* for Internet Explorer, Edge */
|
||||
scrollbar-width: none;
|
||||
/* for Firefox */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#desc_id::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* for Chrome, Safari, and Opera */
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
{% end %}
|
@ -4,7 +4,7 @@
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Crafty Commander</title>
|
||||
<title>Crafty Controller</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Crafty Commander</title>
|
||||
<title>Crafty Controller</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Crafty Commander</title>
|
||||
<title>Crafty Controller</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
|
@ -68,7 +68,7 @@
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
|
||||
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
@ -77,7 +77,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
|
||||
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
@ -86,7 +86,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
|
||||
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
@ -183,7 +183,7 @@
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
|
||||
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
@ -192,7 +192,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
|
||||
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
@ -201,7 +201,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
|
||||
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
|
16
app/migrations/20220618_crafty_api_secret.py
Normal file
16
app/migrations/20220618_crafty_api_secret.py
Normal file
@ -0,0 +1,16 @@
|
||||
import peewee
|
||||
import datetime
|
||||
|
||||
|
||||
def migrate(migrator, db):
|
||||
class CraftySettings(peewee.Model):
|
||||
secret_api_key = peewee.CharField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = "crafty_settings"
|
||||
|
||||
migrator.create_table(CraftySettings)
|
||||
|
||||
|
||||
def rollback(migrator, db):
|
||||
migrator.drop_table("crafty_settings")
|
16
app/migrations/20220620_backup_shutdown.py
Normal file
16
app/migrations/20220620_backup_shutdown.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns("backups", shutdown=peewee.BooleanField(default=False))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns("backups", ["shutdown"])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
@ -352,7 +352,7 @@
|
||||
"createDirQuestion": "Welchen Namen wünschen Sie für das neue Verzeichnis?",
|
||||
"createFile": "Datei erstellen",
|
||||
"createFileQuestion": "Welchen Namen wünschen Sie für die neue Datei?",
|
||||
"default": "Standart",
|
||||
"default": "Standard",
|
||||
"delete": "Löschen",
|
||||
"deleteItemQuestion": "Sind Sie sicher, dass Sie \" + name + \" löschen wollen?",
|
||||
"deleteItemQuestionMessage": "Sie löschen gerade \\\"\" + path + \"\\\"!<br/><br/> Diese Aktion ist unumkehrbar!",
|
||||
@ -454,7 +454,7 @@
|
||||
"buildServer": "Server erstellen!",
|
||||
"clickRoot": "Hier klicken, um das root Verzeichniss auszuwählen",
|
||||
"close": "Schließen",
|
||||
"defaultPort": "25565 (Standart)",
|
||||
"defaultPort": "25565 (Standard)",
|
||||
"downloading": "Server herunterladen...",
|
||||
"explainRoot": "Bitte klicken Sie auf die Schaltfläche unterhalb, um das Stammverzeichnis Ihres Servers innerhalb des Archivs auszuwählen",
|
||||
"importing": "Server importieren...",
|
||||
|
@ -271,7 +271,8 @@
|
||||
"save": "Save",
|
||||
"size": "Size",
|
||||
"storageLocation": "Storage Location",
|
||||
"storageLocationDesc": "Where do you want to store backups?"
|
||||
"storageLocationDesc": "Where do you want to store backups?",
|
||||
"shutdown": "Shutdown server for duration of backup"
|
||||
},
|
||||
"serverConfig": {
|
||||
"bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.",
|
||||
@ -300,6 +301,9 @@
|
||||
"serverCrashDetection": "Server Crash Detection",
|
||||
"serverExecutable": "Server Executable",
|
||||
"serverExecutableDesc": "The server's executable file",
|
||||
"javaVersion": "Override current Java Version",
|
||||
"javaVersionDesc": "If you're going to override java. Make sure your current java path in 'execution command' is wrapped in quotes (default 'java' variable excluded)",
|
||||
"javaNoChange": "Do Not Override",
|
||||
"serverExecutionCommand": "Server Execution Command",
|
||||
"serverExecutionCommandDesc": "What will be launched in a hidden terminal",
|
||||
"serverIP": "Server IP",
|
||||
|
5
main.py
5
main.py
@ -7,6 +7,7 @@ import argparse
|
||||
import logging.config
|
||||
import signal
|
||||
import peewee
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
|
||||
from app.classes.shared.import3 import Import3
|
||||
from app.classes.shared.console import Console
|
||||
@ -132,9 +133,9 @@ if __name__ == "__main__":
|
||||
installer.default_settings()
|
||||
else:
|
||||
Console.debug("Existing install detected")
|
||||
|
||||
file_helper = FileHelpers(helper)
|
||||
# now the tables are created, we can load the tasks_manager and server controller
|
||||
controller = Controller(database, helper)
|
||||
controller = Controller(database, helper, file_helper)
|
||||
import3 = Import3(helper, controller)
|
||||
tasks_manager = TasksManager(helper, controller)
|
||||
tasks_manager.start_webserver()
|
||||
|
Loading…
Reference in New Issue
Block a user