Merge branch 'dev' of gitlab.com:crafty-controller/crafty-commander into dev
:P I had some really old changes
@ -1,8 +1,15 @@
|
||||
# docker related
|
||||
docker/
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
|
||||
# git & gitlab related
|
||||
.git/
|
||||
.gitignore
|
||||
.gitlab-ci.yml
|
||||
|
||||
# root
|
||||
crafty_commander.exe
|
||||
DBCHANGES.md
|
||||
docker-compose.yml.example
|
||||
|
@ -1,15 +1,16 @@
|
||||
stages:
|
||||
- win-dev
|
||||
- win-prod
|
||||
- docker-dev
|
||||
- docker-prod
|
||||
- prod-deployment
|
||||
- dev-deployment
|
||||
|
||||
variables:
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
|
||||
docker-build-dev:
|
||||
image: docker:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--experimental"]
|
||||
stage: docker-dev
|
||||
stage: dev-deployment
|
||||
tags:
|
||||
- docker
|
||||
rules:
|
||||
@ -26,13 +27,15 @@ docker-build-dev:
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
|
||||
docker version
|
||||
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
tag=":$CI_COMMIT_REF_SLUG"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
- docker buildx create --use --name zedBuilder
|
||||
- docker context create tls-environment
|
||||
- docker buildx create --name zedBuilder --use tls-environment
|
||||
- docker buildx build
|
||||
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1
|
||||
@ -42,6 +45,7 @@ docker-build-dev:
|
||||
after_script:
|
||||
- |
|
||||
docker buildx rm zedBuilder && echo "Successfully Stopped builder instance" || echo "Failed to stop builder instance."
|
||||
docker context rm tls-environment || true
|
||||
echo "Please review multi-arch manifests are present:"
|
||||
docker buildx imagetools inspect "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
|
||||
|
||||
@ -49,8 +53,7 @@ docker-build-prod:
|
||||
image: docker:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--experimental"]
|
||||
stage: docker-prod
|
||||
stage: prod-deployment
|
||||
tags:
|
||||
- docker
|
||||
rules:
|
||||
@ -67,13 +70,15 @@ docker-build-prod:
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
|
||||
docker version
|
||||
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
tag=""
|
||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||
- docker buildx create --use --name zedBuilder
|
||||
- docker context create tls-environment
|
||||
- docker buildx create --name zedBuilder --use tls-environment
|
||||
- docker buildx build
|
||||
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1
|
||||
@ -83,11 +88,12 @@ docker-build-prod:
|
||||
after_script:
|
||||
- |
|
||||
docker buildx rm zedBuilder && echo "Successfully Stopped builder instance" || echo "Failed to stop builder instance."
|
||||
docker context rm tls-environment || true
|
||||
echo "Please review multi-arch manifests are present:"
|
||||
docker buildx imagetools inspect "$CI_REGISTRY_IMAGE${tag}"
|
||||
|
||||
win-dev-build:
|
||||
stage: win-dev
|
||||
stage: dev-deployment
|
||||
tags:
|
||||
- win64
|
||||
cache:
|
||||
@ -111,7 +117,11 @@ win-dev-build:
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
--collect-all tzlocal
|
||||
--collect-all tzdata
|
||||
--collect-all apscheduler
|
||||
artifacts:
|
||||
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
@ -121,7 +131,7 @@ win-dev-build:
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build
|
||||
|
||||
win-prod-build:
|
||||
stage: win-prod
|
||||
stage: prod-deployment
|
||||
tags:
|
||||
- win64
|
||||
cache:
|
||||
@ -145,11 +155,15 @@ win-prod-build:
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
--collect-all tzlocal
|
||||
--collect-all tzdata
|
||||
--collect-all apscheduler
|
||||
artifacts:
|
||||
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
exclude:
|
||||
- app\classes\**\*
|
||||
# Download latest:
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build
|
||||
|
12
Dockerfile
@ -2,12 +2,16 @@ FROM python:alpine
|
||||
|
||||
LABEL maintainer="Dockerfile created by Zedifus <https://gitlab.com/zedifus>"
|
||||
|
||||
# Install Packages, Build Dependencies & Garbage Collect & Harden
|
||||
# (Testing repo is needed because jre16 is new)
|
||||
# Security Patch for CVE-2021-44228
|
||||
ENV LOG4J_FORMAT_MSG_NO_LOOKUPS=true
|
||||
|
||||
# Install Packages & Garbage Collect Compile Deps & Harden
|
||||
COPY requirements.txt /commander/requirements.txt
|
||||
RUN apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing openssl-dev rust cargo gcc musl-dev libffi-dev make openjdk8-jre-base openjdk11-jre-headless openjdk16-jre-headless mariadb-dev \
|
||||
RUN apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/latest-stable/community \
|
||||
gcc musl-dev libffi-dev make rust cargo openssl-dev llvm11-libs \
|
||||
openjdk8-jre-base openjdk11-jre-headless openjdk16-jre-headless openjdk17-jre-headless mariadb-dev \
|
||||
&& pip3 install --no-cache-dir -r /commander/requirements.txt \
|
||||
&& apk del --no-cache gcc musl-dev libffi-dev make rust cargo openssl-dev \
|
||||
&& apk del --no-cache gcc musl-dev libffi-dev make rust cargo openssl-dev llvm11-libs \
|
||||
&& rm -rf /sbin/apk \
|
||||
&& rm -rf /etc/apk \
|
||||
&& rm -rf /lib/apk \
|
||||
|
11
README.md
@ -1,4 +1,4 @@
|
||||
# Crafty Controller 4.0.0-alpha.2
|
||||
# Crafty Controller 4.0.0-alpha.3
|
||||
> Python based Control Panel for your Minecraft Server
|
||||
|
||||
## What is Crafty Controller?
|
||||
@ -46,6 +46,15 @@ When you have this just run:
|
||||
```bash
|
||||
$ docker login registry.gitlab.com -u <username> -p <token>
|
||||
```
|
||||
or
|
||||
```bash
|
||||
$ echo <token> | docker login registry.gitlab.com -u <username> --password-stdin
|
||||
```
|
||||
or
|
||||
```bash
|
||||
$ cat ~/my_password.txt | docker login registry.gitlab.com -u <username> --password-stdin
|
||||
```
|
||||
|
||||
Then use one of the following methods:
|
||||
#### docker-compose.yml
|
||||
```yml
|
||||
|
@ -91,6 +91,10 @@ class Management_Controller:
|
||||
def get_scheduled_task(schedule_id):
|
||||
return management_helper.get_scheduled_task(schedule_id)
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task_model(schedule_id):
|
||||
return management_helper.get_scheduled_task_model(schedule_id)
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_by_server(server_id):
|
||||
return management_helper.get_schedules_by_server(server_id)
|
||||
@ -111,5 +115,5 @@ class Management_Controller:
|
||||
return management_helper.get_backup_config(server_id)
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
return management_helper.set_backup_config(server_id, backup_path, max_backups, auto_enabled)
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None):
|
||||
return management_helper.set_backup_config(server_id, backup_path, max_backups)
|
||||
|
@ -51,6 +51,17 @@ class Server_Perms_Controller:
|
||||
def add_role_server(server_id, role_id, rs_permissions="00000000"):
|
||||
return server_permissions.add_role_server(server_id, role_id, rs_permissions)
|
||||
|
||||
@staticmethod
|
||||
def get_server_roles(server_id):
|
||||
return server_permissions.get_server_roles(server_id)
|
||||
|
||||
@staticmethod
|
||||
def backup_role_swap(old_server_id, new_server_id):
|
||||
role_list = server_permissions.get_server_roles(old_server_id)
|
||||
for role in role_list:
|
||||
server_permissions.add_role_server(new_server_id, role.role_id, server_permissions.get_permissions_mask(int(role.role_id), int(old_server_id)))
|
||||
#server_permissions.add_role_server(new_server_id, role.role_id, '00001000')
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Permissions Methods
|
||||
#************************************************************************************************
|
||||
|
@ -36,6 +36,14 @@ class Servers_Controller:
|
||||
def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
return servers_helper.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
|
||||
|
||||
@staticmethod
|
||||
def get_server_obj(server_id):
|
||||
return servers_helper.get_server_obj(server_id)
|
||||
|
||||
@staticmethod
|
||||
def update_server(server_obj):
|
||||
return servers_helper.update_server(server_obj)
|
||||
|
||||
@staticmethod
|
||||
def remove_server(server_id):
|
||||
roles_list = server_permissions.get_roles_from_server(server_id)
|
||||
@ -136,6 +144,10 @@ class Servers_Controller:
|
||||
def get_waiting_start(server_id):
|
||||
return servers_helper.get_waiting_start(server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_update_status(server_id):
|
||||
return servers_helper.get_update_status(server_id)
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Helpers Methods
|
||||
#************************************************************************************************
|
||||
@ -146,7 +158,7 @@ class Servers_Controller:
|
||||
path = os.path.join(server_path, 'banned-players.json')
|
||||
|
||||
try:
|
||||
with open(path) as file:
|
||||
with open(helper.get_os_understandable_path(path)) as file:
|
||||
content = file.read()
|
||||
file.close()
|
||||
except Exception as ex:
|
||||
@ -170,7 +182,7 @@ class Servers_Controller:
|
||||
))
|
||||
for log_file in log_files:
|
||||
log_file_path = os.path.join(logs_path, log_file)
|
||||
if self.check_file_exists(log_file_path) and \
|
||||
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
|
||||
if helper.check_file_exists(log_file_path) and \
|
||||
helper.is_file_older_than_x_days(log_file_path, logs_delete_after):
|
||||
os.remove(log_file_path)
|
||||
|
||||
|
@ -47,6 +47,10 @@ class Users_Controller:
|
||||
def user_query(user_id):
|
||||
return users_helper.user_query(user_id)
|
||||
|
||||
@staticmethod
|
||||
def set_support_path(user_id, support_path):
|
||||
users_helper.set_support_path(user_id, support_path)
|
||||
|
||||
@staticmethod
|
||||
def update_user(user_id, user_data={}, user_crafty_data={}):
|
||||
base_data = users_helper.get_user(user_id)
|
||||
@ -94,8 +98,8 @@ class Users_Controller:
|
||||
users_helper.update_user(user_id, up_data)
|
||||
|
||||
@staticmethod
|
||||
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
|
||||
return users_helper.add_user(username, password=password, api_token=api_token, enabled=enabled, superuser=superuser)
|
||||
def add_user(username, password=None, email="default@example.com", api_token=None, enabled=True, superuser=False):
|
||||
return users_helper.add_user(username, password=password, email=email, api_token=api_token, enabled=enabled, superuser=superuser)
|
||||
|
||||
@staticmethod
|
||||
def remove_user(user_id):
|
||||
|
@ -1,66 +1,66 @@
|
||||
import pprint
|
||||
import os
|
||||
|
||||
class ServerProps:
|
||||
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.props = self._parse()
|
||||
|
||||
def _parse(self):
|
||||
"""Loads and parses the file speified in self.filepath"""
|
||||
with open(self.filepath) as fp:
|
||||
line = fp.readline()
|
||||
d = {}
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
while line:
|
||||
if '#' != line[0]:
|
||||
s = line
|
||||
s1 = s[:s.find('=')]
|
||||
if '\n' in s:
|
||||
s2 = s[s.find('=')+1:s.find('\n')]
|
||||
else:
|
||||
s2 = s[s.find('=')+1:]
|
||||
d[s1] = s2
|
||||
else:
|
||||
with open(".header", "a+") as h:
|
||||
h.write(line)
|
||||
line = fp.readline()
|
||||
return d
|
||||
|
||||
def print(self):
|
||||
"""Prints the properties dictionary (using pprint)"""
|
||||
pprint.pprint(self.props)
|
||||
|
||||
def get(self):
|
||||
"""Returns the properties dictionary"""
|
||||
return self.props
|
||||
|
||||
def update(self, key, val):
|
||||
"""Updates property in the properties dictionary [ update("pvp", "true") ] and returns boolean condition"""
|
||||
if key in self.props.keys():
|
||||
self.props[key] = val
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def save(self):
|
||||
"""Writes to the new file"""
|
||||
with open(self.filepath, "a+") as f:
|
||||
f.truncate(0)
|
||||
with open(".header") as header:
|
||||
line = header.readline()
|
||||
while line:
|
||||
f.write(line)
|
||||
line = header.readline()
|
||||
header.close()
|
||||
for key, value in self.props.items():
|
||||
f.write(key + "=" + value + "\n")
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
|
||||
@staticmethod
|
||||
def cleanup():
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
import pprint
|
||||
import os
|
||||
|
||||
class ServerProps:
|
||||
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.props = self._parse()
|
||||
|
||||
def _parse(self):
|
||||
"""Loads and parses the file specified in self.filepath"""
|
||||
with open(self.filepath) as fp:
|
||||
line = fp.readline()
|
||||
d = {}
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
while line:
|
||||
if '#' != line[0]:
|
||||
s = line
|
||||
s1 = s[:s.find('=')]
|
||||
if '\n' in s:
|
||||
s2 = s[s.find('=')+1:s.find('\n')]
|
||||
else:
|
||||
s2 = s[s.find('=')+1:]
|
||||
d[s1] = s2
|
||||
else:
|
||||
with open(".header", "a+") as h:
|
||||
h.write(line)
|
||||
line = fp.readline()
|
||||
return d
|
||||
|
||||
def print(self):
|
||||
"""Prints the properties dictionary (using pprint)"""
|
||||
pprint.pprint(self.props)
|
||||
|
||||
def get(self):
|
||||
"""Returns the properties dictionary"""
|
||||
return self.props
|
||||
|
||||
def update(self, key, val):
|
||||
"""Updates property in the properties dictionary [ update("pvp", "true") ] and returns boolean condition"""
|
||||
if key in self.props.keys():
|
||||
self.props[key] = val
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def save(self):
|
||||
"""Writes to the new file"""
|
||||
with open(self.filepath, "a+") as f:
|
||||
f.truncate(0)
|
||||
with open(".header") as header:
|
||||
line = header.readline()
|
||||
while line:
|
||||
f.write(line)
|
||||
line = header.readline()
|
||||
header.close()
|
||||
for key, value in self.props.items():
|
||||
f.write(key + "=" + value + "\n")
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
|
||||
@staticmethod
|
||||
def cleanup():
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
|
@ -45,15 +45,16 @@ class Stats:
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _get_process_stats(process_pid: int):
|
||||
if process_pid is None:
|
||||
def _get_process_stats(process):
|
||||
if process is None:
|
||||
process_stats = {
|
||||
'cpu_usage': 0,
|
||||
'memory_usage': 0,
|
||||
'mem_percentage': 0
|
||||
}
|
||||
return process_stats
|
||||
|
||||
else:
|
||||
process_pid = process.pid
|
||||
try:
|
||||
p = psutil.Process(process_pid)
|
||||
dummy = p.cpu_percent()
|
||||
@ -91,7 +92,7 @@ class Stats:
|
||||
# print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type","Mount"))
|
||||
|
||||
for part in psutil.disk_partitions(all=False):
|
||||
if os.name == 'nt':
|
||||
if helper.is_os_windows():
|
||||
if 'cdrom' in part.opts or part.fstype == '':
|
||||
# skip cd-rom drives with no disk in it; they may raise
|
||||
# ENOENT, pop-up a Windows GUI error for a non-ready
|
||||
@ -162,7 +163,7 @@ class Stats:
|
||||
}
|
||||
|
||||
return ping_data
|
||||
|
||||
|
||||
def get_server_players(self, server_id):
|
||||
|
||||
server = servers_helper.get_server_data_by_id(server_id)
|
||||
@ -217,7 +218,7 @@ class Stats:
|
||||
world_path = os.path.join(server_data.get('path', None), world_name)
|
||||
|
||||
# process stats
|
||||
p_stats = self._get_process_stats(server_obj.PID)
|
||||
p_stats = self._get_process_stats(server_obj.process)
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
internal_ip = server['server_ip']
|
||||
@ -256,7 +257,7 @@ class Stats:
|
||||
server_stats_list.append(server_stats)
|
||||
|
||||
return server_stats_list
|
||||
|
||||
|
||||
def get_raw_server_stats(self, server_id):
|
||||
|
||||
server_stats = {}
|
||||
@ -275,7 +276,7 @@ class Stats:
|
||||
world_path = os.path.join(server_data.get('path', None), world_name)
|
||||
|
||||
# process stats
|
||||
p_stats = self._get_process_stats(server_obj.PID)
|
||||
p_stats = self._get_process_stats(server_obj.process)
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
#internal_ip = server['server_ip']
|
||||
|
@ -112,6 +112,8 @@ class Schedules(Model):
|
||||
start_time = CharField(null=True)
|
||||
command = CharField(null=True)
|
||||
comment = CharField()
|
||||
one_time = BooleanField(default=False)
|
||||
cron_string = CharField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = 'schedules'
|
||||
@ -125,14 +127,12 @@ class Backups(Model):
|
||||
directories = CharField(null=True)
|
||||
max_backups = IntegerField()
|
||||
server_id = ForeignKeyField(Servers, backref='backups_server')
|
||||
schedule_id = ForeignKeyField(Schedules, backref='backups_schedule')
|
||||
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
database = database
|
||||
|
||||
class helpers_management:
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Host_Stats Methods
|
||||
#************************************************************************************************
|
||||
@ -165,7 +165,7 @@ class helpers_management:
|
||||
Commands.update({
|
||||
Commands.executed: True
|
||||
}).where(Commands.command_id == command_id).execute()
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Audit_Log Methods
|
||||
#************************************************************************************************
|
||||
@ -173,7 +173,7 @@ class helpers_management:
|
||||
def get_actity_log():
|
||||
q = Audit_Log.select()
|
||||
return db_helper.return_db_rows(q)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log(user_id, log_msg, server_id=None, source_ip=None):
|
||||
logger.debug("Adding to audit log User:{} - Message: {} ".format(user_id, log_msg))
|
||||
@ -205,7 +205,7 @@ class helpers_management:
|
||||
# Schedules Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True):
|
||||
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True, one_time=False, cron_string='* * * * *'):
|
||||
sch_id = Schedules.insert({
|
||||
Schedules.server_id: server_id,
|
||||
Schedules.action: action,
|
||||
@ -214,7 +214,10 @@ class helpers_management:
|
||||
Schedules.interval_type: interval_type,
|
||||
Schedules.start_time: start_time,
|
||||
Schedules.command: command,
|
||||
Schedules.comment: comment
|
||||
Schedules.comment: comment,
|
||||
Schedules.one_time: one_time,
|
||||
Schedules.cron_string: cron_string
|
||||
|
||||
}).execute()
|
||||
return sch_id
|
||||
|
||||
@ -227,10 +230,18 @@ class helpers_management:
|
||||
def update_scheduled_task(schedule_id, updates):
|
||||
Schedules.update(updates).where(Schedules.schedule_id == schedule_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def delete_scheduled_task_by_server(server_id):
|
||||
Schedules.delete().where(Schedules.server_id == int(server_id)).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task(schedule_id):
|
||||
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task_model(schedule_id):
|
||||
return Schedules.select().where(Schedules.schedule_id == schedule_id).get()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_by_server(server_id):
|
||||
return Schedules.select().where(Schedules.server_id == server_id).execute()
|
||||
@ -249,12 +260,11 @@ class helpers_management:
|
||||
@staticmethod
|
||||
def get_backup_config(server_id):
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Servers)[0]
|
||||
conf = {
|
||||
"backup_path": row.server_id.backup_path,
|
||||
"directories": row.directories,
|
||||
"max_backups": row.max_backups,
|
||||
"auto_enabled": row.schedule_id.enabled,
|
||||
"server_id": row.server_id.server_id
|
||||
}
|
||||
except IndexError:
|
||||
@ -262,16 +272,15 @@ class helpers_management:
|
||||
"backup_path": None,
|
||||
"directories": None,
|
||||
"max_backups": 0,
|
||||
"auto_enabled": True,
|
||||
"server_id": server_id
|
||||
}
|
||||
return conf
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None):
|
||||
logger.debug("Updating server {} backup config with {}".format(server_id, locals()))
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Servers)[0]
|
||||
new_row = False
|
||||
conf = {}
|
||||
schd = {}
|
||||
@ -281,19 +290,9 @@ class helpers_management:
|
||||
"max_backups": 0,
|
||||
"server_id": server_id
|
||||
}
|
||||
schd = {
|
||||
"enabled": True,
|
||||
"action": "backup_server",
|
||||
"interval_type": "days",
|
||||
"interval": 1,
|
||||
"start_time": "00:00",
|
||||
"server_id": server_id,
|
||||
"comment": "Default backup job"
|
||||
}
|
||||
new_row = True
|
||||
if max_backups is not None:
|
||||
conf['max_backups'] = max_backups
|
||||
schd['enabled'] = bool(auto_enabled)
|
||||
if not new_row:
|
||||
with database.atomic():
|
||||
if backup_path is not None:
|
||||
@ -301,15 +300,12 @@ class helpers_management:
|
||||
else:
|
||||
u1 = 0
|
||||
u2 = Backups.update(conf).where(Backups.server_id == server_id).execute()
|
||||
u3 = Schedules.update(schd).where(Schedules.schedule_id == row.schedule_id).execute()
|
||||
logger.debug("Updating existing backup record. {}+{}+{} rows affected".format(u1, u2, u3))
|
||||
logger.debug("Updating existing backup record. {}+{} rows affected".format(u1, u2))
|
||||
else:
|
||||
with database.atomic():
|
||||
conf["server_id"] = server_id
|
||||
if backup_path is not None:
|
||||
u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
|
||||
s = Schedules.create(**schd)
|
||||
conf['schedule_id'] = s.schedule_id
|
||||
b = Backups.create(**conf)
|
||||
logger.debug("Creating new backup record.")
|
||||
|
||||
|
@ -118,14 +118,22 @@ class Permissions_Servers:
|
||||
@staticmethod
|
||||
def get_permissions_mask(role_id, server_id):
|
||||
permissions_mask = ''
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).execute()
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).get()
|
||||
permissions_mask = role_server.permissions
|
||||
return permissions_mask
|
||||
|
||||
@staticmethod
|
||||
def get_server_roles(server_id):
|
||||
role_list = []
|
||||
roles = Role_Servers.select().where(Role_Servers.server_id == server_id).execute()
|
||||
for role in roles:
|
||||
role_list.append(role.role_id)
|
||||
return role_list
|
||||
|
||||
@staticmethod
|
||||
def get_role_permissions_list(role_id):
|
||||
permissions_mask = '00000000'
|
||||
role_server = Role_Servers.get_or_none(role_id)
|
||||
role_server = Role_Servers.get_or_none(Role_Servers.role_id == role_id)
|
||||
if role_server is not None:
|
||||
permissions_mask = role_server.permissions
|
||||
permissions_list = server_permissions.get_permissions(permissions_mask)
|
||||
@ -157,7 +165,10 @@ class Permissions_Servers:
|
||||
else:
|
||||
roles_list = users_helper.get_user_roles_id(user_id)
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == int(server_id)).execute()
|
||||
permissions_mask = role_server[0].permissions
|
||||
if len(role_server) > 0:
|
||||
permissions_mask = role_server[0].permissions
|
||||
else:
|
||||
permissions_mask = '00000000'
|
||||
permissions_list = server_permissions.get_permissions(permissions_mask)
|
||||
return permissions_list
|
||||
|
||||
|
@ -77,6 +77,7 @@ class Server_Stats(Model):
|
||||
version = CharField(default="")
|
||||
updating = BooleanField(default=False)
|
||||
waiting_start = BooleanField(default=False)
|
||||
first_run = BooleanField(default=True)
|
||||
|
||||
|
||||
class Meta:
|
||||
@ -88,7 +89,7 @@ class Server_Stats(Model):
|
||||
# Servers Class
|
||||
#************************************************************************************************
|
||||
class helper_servers:
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Generic Servers Methods
|
||||
#************************************************************************************************
|
||||
@ -109,6 +110,15 @@ class helper_servers:
|
||||
Servers.backup_path: backup_path
|
||||
}).execute()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_server_obj(server_id):
|
||||
return Servers.get_by_id(server_id)
|
||||
|
||||
@staticmethod
|
||||
def update_server(server_obj):
|
||||
return server_obj.save()
|
||||
|
||||
@staticmethod
|
||||
def remove_server(server_id):
|
||||
with database.atomic():
|
||||
@ -173,6 +183,26 @@ class helper_servers:
|
||||
with database.atomic():
|
||||
Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_update_status(server_id):
|
||||
waiting_start = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
|
||||
return waiting_start.waiting_start
|
||||
|
||||
@staticmethod
|
||||
def set_first_run(server_id):
|
||||
#Sets first run to false
|
||||
try:
|
||||
row = Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
except Exception as ex:
|
||||
logger.error("Database entry not found. ".format(ex))
|
||||
with database.atomic():
|
||||
Server_Stats.update(first_run=False).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_first_run(server_id):
|
||||
first_run = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
|
||||
return first_run.first_run
|
||||
|
||||
@staticmethod
|
||||
def get_TTL_without_player(server_id):
|
||||
last_stat = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).first()
|
||||
@ -186,7 +216,7 @@ class helper_servers:
|
||||
if (time_limit == -1) or (ttl_no_players > time_limit):
|
||||
can = True
|
||||
return can
|
||||
|
||||
|
||||
@staticmethod
|
||||
def set_waiting_start(server_id, value):
|
||||
try:
|
||||
|
@ -38,10 +38,12 @@ class Users(Model):
|
||||
last_ip = CharField(default="")
|
||||
username = CharField(default="", unique=True, index=True)
|
||||
password = CharField(default="")
|
||||
email = CharField(default="default@example.com")
|
||||
enabled = BooleanField(default=True)
|
||||
superuser = BooleanField(default=False)
|
||||
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
|
||||
lang = CharField(default="en_EN")
|
||||
support_logs = CharField(default = '')
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
@ -70,7 +72,7 @@ class helper_users:
|
||||
|
||||
@staticmethod
|
||||
def get_all_users():
|
||||
query = Users.select()
|
||||
query = Users.select().where(Users.username != "system")
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
@ -79,8 +81,6 @@ class helper_users:
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_by_name(username):
|
||||
if username == "SYSTEM":
|
||||
return 0
|
||||
try:
|
||||
return (Users.get(Users.username == username)).user_id
|
||||
except DoesNotExist:
|
||||
@ -108,17 +108,19 @@ class helper_users:
|
||||
if user_id == 0:
|
||||
return {
|
||||
'user_id': 0,
|
||||
'created': None,
|
||||
'last_login': None,
|
||||
'last_update': None,
|
||||
'created': '10/24/2019, 11:34:00',
|
||||
'last_login': '10/24/2019, 11:34:00',
|
||||
'last_update': '10/24/2019, 11:34:00',
|
||||
'last_ip': "127.27.23.89",
|
||||
'username': "SYSTEM",
|
||||
'password': None,
|
||||
'email': "default@example.com",
|
||||
'enabled': True,
|
||||
'superuser': False,
|
||||
'superuser': True,
|
||||
'api_token': None,
|
||||
'roles': [],
|
||||
'servers': [],
|
||||
'support_logs': '',
|
||||
}
|
||||
user = model_to_dict(Users.get(Users.user_id == user_id))
|
||||
|
||||
@ -130,8 +132,15 @@ class helper_users:
|
||||
#logger.debug("user: ({}) {}".format(user_id, {}))
|
||||
return {}
|
||||
|
||||
def check_system_user(user_id):
|
||||
try:
|
||||
Users.get(Users.user_id == user_id).user_id == user_id
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
|
||||
def add_user(username, password=None, email=None, api_token=None, enabled=True, superuser=False):
|
||||
if password is not None:
|
||||
pw_enc = helper.encode_pass(password)
|
||||
else:
|
||||
@ -144,6 +153,7 @@ class helper_users:
|
||||
user_id = Users.insert({
|
||||
Users.username: username.lower(),
|
||||
Users.password: pw_enc,
|
||||
Users.email: email,
|
||||
Users.api_token: api_token,
|
||||
Users.enabled: enabled,
|
||||
Users.superuser: superuser,
|
||||
@ -163,6 +173,10 @@ class helper_users:
|
||||
user = Users.get(Users.user_id == user_id)
|
||||
return user.delete_instance()
|
||||
|
||||
@staticmethod
|
||||
def set_support_path(user_id, support_path):
|
||||
Users.update(support_logs = support_path).where(Users.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def user_id_exists(user_id):
|
||||
if not users_helper.get_user(user_id):
|
||||
|
@ -2,9 +2,11 @@ import os
|
||||
import sys
|
||||
import cmd
|
||||
import time
|
||||
|
||||
import threading
|
||||
import logging
|
||||
|
||||
from app.classes.shared.tasks import TasksManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from app.classes.shared.console import console
|
||||
@ -34,18 +36,8 @@ class MainPrompt(cmd.Cmd, object):
|
||||
def emptyline():
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _clean_shutdown():
|
||||
exit_file = os.path.join(helper.root_dir, "exit.txt")
|
||||
try:
|
||||
with open(exit_file, 'w') as f:
|
||||
f.write("exit")
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Unable to write exit file due to error: {}".format(e))
|
||||
console.critical("Unable to write exit file due to error: {}".format(e))
|
||||
|
||||
def do_exit(self, line):
|
||||
self.tasks_manager._main_graceful_exit()
|
||||
self.universal_exit()
|
||||
|
||||
def do_migrations(self, line):
|
||||
@ -69,11 +61,14 @@ class MainPrompt(cmd.Cmd, object):
|
||||
else:
|
||||
console.info('Unknown migration command')
|
||||
|
||||
def do_threads(self, line):
|
||||
for thread in threading.enumerate():
|
||||
print(f'Name: {thread.name} IDENT: {thread.ident}')
|
||||
|
||||
def universal_exit(self):
|
||||
logger.info("Stopping all server daemons / threads")
|
||||
console.info("Stopping all server daemons / threads - This may take a few seconds")
|
||||
websocket_helper.disconnect_all()
|
||||
self._clean_shutdown()
|
||||
console.info('Waiting for main thread to stop')
|
||||
while True:
|
||||
if self.tasks_manager.get_main_thread_run_status():
|
||||
|
@ -15,6 +15,10 @@ import zipfile
|
||||
import pathlib
|
||||
import shutil
|
||||
from requests import get
|
||||
from contextlib import suppress
|
||||
import ctypes
|
||||
import telnetlib
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
@ -34,6 +38,11 @@ except ModuleNotFoundError as e:
|
||||
sys.exit(1)
|
||||
|
||||
class Helpers:
|
||||
allowed_quotes = [
|
||||
"\"",
|
||||
"'",
|
||||
"`"
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.root_dir = os.path.abspath(os.path.curdir)
|
||||
@ -76,6 +85,9 @@ class Helpers:
|
||||
logger.error("{} does not exist".format(file))
|
||||
return True
|
||||
|
||||
def get_servers_root_dir(self):
|
||||
return self.servers_dir
|
||||
|
||||
@staticmethod
|
||||
def check_internet():
|
||||
try:
|
||||
@ -87,18 +99,90 @@ class Helpers:
|
||||
@staticmethod
|
||||
def check_port(server_port):
|
||||
try:
|
||||
host_public = get('https://api.ipify.org').text
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10.0)
|
||||
result = sock.connect_ex((host_public ,server_port))
|
||||
sock.close()
|
||||
if result == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as err:
|
||||
ip = get('https://api.ipify.org').content.decode('utf8')
|
||||
except:
|
||||
ip = 'google.com'
|
||||
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
a_socket.settimeout(20.0)
|
||||
|
||||
location = (ip, server_port)
|
||||
result_of_check = a_socket.connect_ex(location)
|
||||
|
||||
a_socket.close()
|
||||
|
||||
if result_of_check == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_server_conn(server_port):
|
||||
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
a_socket.settimeout(10.0)
|
||||
ip = '127.0.0.1'
|
||||
|
||||
location = (ip, server_port)
|
||||
result_of_check = a_socket.connect_ex(location)
|
||||
a_socket.close()
|
||||
|
||||
if result_of_check == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def cmdparse(cmd_in):
|
||||
# Parse a string into arguments
|
||||
cmd_out = [] # "argv" output array
|
||||
ci = -1 # command index - pointer to the argument we're building in cmd_out
|
||||
np = True # whether we're creating a new argument/parameter
|
||||
esc = False # whether an escape character was encountered
|
||||
stch = None # if we're dealing with a quote, save the quote type here. Nested quotes to be dealt with by the command
|
||||
for c in cmd_in: # for character in string
|
||||
if np == True: # if set, begin a new argument and increment the command index. Continue the loop.
|
||||
if c == ' ':
|
||||
continue
|
||||
else:
|
||||
ci += 1
|
||||
cmd_out.append("")
|
||||
np = False
|
||||
if esc: # if we encountered an escape character on the last loop, append this char regardless of what it is
|
||||
if c not in Helpers.allowed_quotes:
|
||||
cmd_out[ci] += '\\'
|
||||
cmd_out[ci] += c
|
||||
esc = False
|
||||
else:
|
||||
if c == '\\': # if the current character is an escape character, set the esc flag and continue to next loop
|
||||
esc = True
|
||||
elif c == ' ' and stch is None: # if we encounter a space and are not dealing with a quote, set the new argument flag and continue to next loop
|
||||
np = True
|
||||
elif c == stch: # if we encounter the character that matches our start quote, end the quote and continue to next loop
|
||||
stch = None
|
||||
elif stch is None and (c in Helpers.allowed_quotes): # if we're not in the middle of a quote and we get a quotable character, start a quote and proceed to the next loop
|
||||
stch = c
|
||||
else: # else, just store the character in the current arg
|
||||
cmd_out[ci] += c
|
||||
return cmd_out
|
||||
|
||||
def check_for_old_logs(self, db_helper):
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
for server in servers:
|
||||
logs_path = os.path.split(server['log_path'])[0]
|
||||
latest_log_file = os.path.split(server['log_path'])[1]
|
||||
logs_delete_after = int(server['logs_delete_after'])
|
||||
if logs_delete_after == 0:
|
||||
continue
|
||||
|
||||
log_files = list(filter(
|
||||
lambda val: val != latest_log_file,
|
||||
os.listdir(logs_path)
|
||||
))
|
||||
for log_file in log_files:
|
||||
log_file_path = os.path.join(logs_path, log_file)
|
||||
if self.check_file_exists(log_file_path) and \
|
||||
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
|
||||
os.remove(log_file_path)
|
||||
|
||||
def get_setting(self, key, default_return=False):
|
||||
|
||||
try:
|
||||
@ -151,8 +235,8 @@ class Helpers:
|
||||
if r.status_code in [200, 201]:
|
||||
try:
|
||||
data = json.loads(r.content)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error("Failed to load json content with error: {} ".format(e))
|
||||
|
||||
return data
|
||||
|
||||
@ -167,16 +251,6 @@ class Helpers:
|
||||
version_data.get('meta', '?'))
|
||||
return str(version)
|
||||
|
||||
def do_exit(self):
|
||||
exit_file = os.path.join(self.root_dir, 'exit.txt')
|
||||
try:
|
||||
open(exit_file, 'a').close()
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Unable to create exit file!")
|
||||
console.critical("Unable to create exit file!")
|
||||
sys.exit(1)
|
||||
|
||||
def encode_pass(self, password):
|
||||
return self.passhasher.hash(password)
|
||||
|
||||
@ -215,6 +289,19 @@ class Helpers:
|
||||
|
||||
return line
|
||||
|
||||
|
||||
def validate_traversal(self, base_path, filename):
|
||||
logger.debug("Validating traversal (\"{x}\", \"{y}\")".format(x=base_path, y=filename))
|
||||
base = pathlib.Path(base_path).resolve()
|
||||
file = pathlib.Path(filename)
|
||||
fileabs = base.joinpath(file).resolve()
|
||||
cp = pathlib.Path(os.path.commonpath([base, fileabs]))
|
||||
if base == cp:
|
||||
return fileabs
|
||||
else:
|
||||
raise ValueError("Path traversal detected")
|
||||
|
||||
|
||||
def tail_file(self, file_name, number_lines=20):
|
||||
if not self.check_file_exists(file_name):
|
||||
logger.warning("Unable to find file to tail: {}".format(file_name))
|
||||
@ -263,6 +350,18 @@ class Helpers:
|
||||
logger.critical("Unable to write to {} - Error: {}".format(path, e))
|
||||
return False
|
||||
|
||||
def checkRoot(self):
|
||||
if self.is_os_windows():
|
||||
if ctypes.windll.shell32.IsUserAnAdmin() == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
if os.geteuid() == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def unzipFile(self, zip_path):
|
||||
new_dir_list = zip_path.split('/')
|
||||
new_dir = ''
|
||||
@ -315,11 +414,12 @@ class Helpers:
|
||||
logger.critical("Unable to write to {} directory!".format(self.root_dir))
|
||||
sys.exit(1)
|
||||
|
||||
# ensure the log directory is there
|
||||
# ensure the log directory is there
|
||||
try:
|
||||
os.makedirs(os.path.join(self.root_dir, 'logs'))
|
||||
with suppress(FileExistsError):
|
||||
os.makedirs(os.path.join(self.root_dir, 'logs'))
|
||||
except Exception as e:
|
||||
pass
|
||||
console.error("Failed to make logs directory with error: {} ".format(e))
|
||||
|
||||
# ensure the log file is there
|
||||
try:
|
||||
@ -331,8 +431,8 @@ class Helpers:
|
||||
# del any old session.lock file as this is a new session
|
||||
try:
|
||||
os.remove(session_log_file)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error("Deleting Session.lock failed with error: {} ".format(e))
|
||||
|
||||
@staticmethod
|
||||
def get_time_as_string():
|
||||
@ -403,7 +503,9 @@ class Helpers:
|
||||
started = data.get('started')
|
||||
console.critical("Another Crafty Controller agent seems to be running...\npid: {} \nstarted on: {}".format(pid, started))
|
||||
except Exception as e:
|
||||
pass
|
||||
logger.error("Failed to locate existing session.lock with error: {} ".format(e))
|
||||
console.error("Failed to locate existing session.lock with error: {} ".format(e))
|
||||
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
@ -541,6 +643,20 @@ class Helpers:
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def wtol_path(w_path):
|
||||
l_path = w_path.replace('\\', '/')
|
||||
return l_path
|
||||
|
||||
@staticmethod
|
||||
def ltow_path(l_path):
|
||||
w_path = l_path.replace('/', '\\')
|
||||
return w_path
|
||||
|
||||
@staticmethod
|
||||
def get_os_understandable_path(path):
|
||||
return os.path.normpath(path)
|
||||
|
||||
def find_default_password(self):
|
||||
default_file = os.path.join(self.root_dir, "default.json")
|
||||
data = {}
|
||||
@ -558,30 +674,137 @@ class Helpers:
|
||||
|
||||
@staticmethod
|
||||
def generate_tree(folder, output=""):
|
||||
for raw_filename in os.listdir(folder):
|
||||
file_list = os.listdir(folder)
|
||||
file_list = sorted(file_list, key=str.casefold)
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
"""<li class="tree-item" data-path="{}">
|
||||
\n<div data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
|
||||
\n<div id="{}" data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{}span" class="files-tree-title" data-path="{}" data-name="{}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{}
|
||||
</div>
|
||||
\n<ul class="tree-nested">"""\
|
||||
.format(os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
|
||||
|
||||
output += helper.generate_tree(rel)
|
||||
output += '</ul>\n</li>'
|
||||
</span>
|
||||
</div><li>
|
||||
\n"""\
|
||||
.format(os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
|
||||
else:
|
||||
output += """<li
|
||||
class="tree-item tree-ctx-item tree-file"
|
||||
data-path="{}"
|
||||
data-name="{}"
|
||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{}</li>""".format(os.path.join(folder, filename), filename, filename)
|
||||
if filename != "crafty_managed.txt":
|
||||
output += """<li
|
||||
class="tree-item tree-ctx-item tree-file"
|
||||
data-path="{}"
|
||||
data-name="{}"
|
||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{}</li>""".format(os.path.join(folder, filename), filename, filename)
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def generate_dir(folder, output=""):
|
||||
file_list = os.listdir(folder)
|
||||
file_list = sorted(file_list, key=str.casefold)
|
||||
output += \
|
||||
"""<ul class="tree-nested d-block" id="{}ul">"""\
|
||||
.format(folder)
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
"""<li class="tree-item" data-path="{}">
|
||||
\n<div id="{}" data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{}span" class="files-tree-title" data-path="{}" data-name="{}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{}
|
||||
</span>
|
||||
</div><li>"""\
|
||||
.format(os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
|
||||
else:
|
||||
if filename != "crafty_managed.txt":
|
||||
output += """<li
|
||||
class="tree-item tree-ctx-item tree-file"
|
||||
data-path="{}"
|
||||
data-name="{}"
|
||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{}</li>""".format(os.path.join(folder, filename), filename, filename)
|
||||
output += '</ul>\n'
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def generate_zip_tree(folder, output=""):
|
||||
file_list = os.listdir(folder)
|
||||
file_list = sorted(file_list, key=str.casefold)
|
||||
output += \
|
||||
"""<ul class="tree-nested d-block" id="{}ul">"""\
|
||||
.format(folder)
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
"""<li class="tree-item" data-path="{}">
|
||||
\n<div id="{}" data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="radio" name="root_path" value="{}">
|
||||
<span id="{}span" class="files-tree-title" data-path="{}" data-name="{}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{}
|
||||
</span>
|
||||
</input></div><li>
|
||||
\n"""\
|
||||
.format(os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def generate_zip_dir(folder, output=""):
|
||||
file_list = os.listdir(folder)
|
||||
file_list = sorted(file_list, key=str.casefold)
|
||||
output += \
|
||||
"""<ul class="tree-nested d-block" id="{}ul">"""\
|
||||
.format(folder)
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
"""<li class="tree-item" data-path="{}">
|
||||
\n<div id="{}" data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="radio" name="root_path" value="{}">
|
||||
<span id="{}span" class="files-tree-title" data-path="{}" data-name="{}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{}
|
||||
</span>
|
||||
</input></div><li>"""\
|
||||
.format(os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def unzipServer(zip_path, user_id):
|
||||
if helper.check_file_perms(zip_path):
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
#extracts archive to temp directory
|
||||
zip_ref.extractall(tempDir)
|
||||
if user_id:
|
||||
websocket_helper.broadcast_user(user_id, 'send_temp_path',{
|
||||
'path': tempDir
|
||||
})
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def unzip_backup_archive(backup_path, zip_name):
|
||||
zip_path = os.path.join(backup_path, zip_name)
|
||||
if helper.check_file_perms(zip_path):
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
#extracts archive to temp directory
|
||||
zip_ref.extractall(tempDir)
|
||||
return tempDir
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def in_path(parent_path, child_path):
|
||||
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
|
||||
|
@ -1,13 +1,19 @@
|
||||
import os
|
||||
import pathlib
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.models.users import helper_users
|
||||
from peewee import DoesNotExist
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
from app.classes.models.management import helpers_management
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
@ -69,7 +75,7 @@ class Controller:
|
||||
continue
|
||||
|
||||
# if this server path no longer exists - let's warn and bomb out
|
||||
if not helper.check_path_exists(s['path']):
|
||||
if not helper.check_path_exists(helper.get_os_understandable_path(s['path'])):
|
||||
logger.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'],
|
||||
s['path']))
|
||||
|
||||
@ -77,7 +83,7 @@ class Controller:
|
||||
s['path']))
|
||||
continue
|
||||
|
||||
settings_file = os.path.join(s['path'], 'server.properties')
|
||||
settings_file = os.path.join(helper.get_os_understandable_path(s['path']), 'server.properties')
|
||||
|
||||
# if the properties file isn't there, let's warn
|
||||
if not helper.check_file_exists(settings_file):
|
||||
@ -116,6 +122,59 @@ class Controller:
|
||||
server_obj = self.get_server_obj(server_id)
|
||||
server_obj.reload_server_settings()
|
||||
|
||||
@staticmethod
|
||||
def check_system_user():
|
||||
if helper_users.get_user_id_by_name("system") is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_project_root(self, root_dir):
|
||||
self.project_root = root_dir
|
||||
|
||||
|
||||
def package_support_logs(self, exec_user):
|
||||
time.sleep(5)
|
||||
websocket_helper.broadcast_user(exec_user['user_id'], 'notification', 'Preparing your support logs')
|
||||
tempDir = tempfile.mkdtemp()
|
||||
tempZipStorage = tempfile.mkdtemp()
|
||||
full_temp = os.path.join(tempDir, 'support_logs')
|
||||
os.mkdir(full_temp)
|
||||
tempZipStorage = os.path.join(tempZipStorage, "support_logs")
|
||||
crafty_path = os.path.join(full_temp, "crafty")
|
||||
os.mkdir(crafty_path)
|
||||
server_path = os.path.join(full_temp, "server")
|
||||
os.mkdir(server_path)
|
||||
if exec_user['superuser']:
|
||||
auth_servers = self.servers.get_all_defined_servers()
|
||||
else:
|
||||
user_servers = self.servers.get_authorized_servers(int(exec_user['user_id']))
|
||||
auth_servers = []
|
||||
for server in user_servers:
|
||||
if Enum_Permissions_Server.Logs in self.server_perms.get_user_permissions_list(exec_user['user_id'], server["server_id"]):
|
||||
auth_servers.append(server)
|
||||
else:
|
||||
logger.info("Logs permission not available for server {}. Skipping.".format(server["server_name"]))
|
||||
#we'll iterate through our list of log paths from auth servers.
|
||||
for server in auth_servers:
|
||||
final_path = os.path.join(server_path, str(server['server_name']))
|
||||
os.mkdir(final_path)
|
||||
shutil.copy(server['log_path'], final_path)
|
||||
#Copy crafty logs to archive dir
|
||||
full_log_name = os.path.join(crafty_path, 'logs')
|
||||
shutil.copytree(os.path.join(self.project_root, 'logs'), full_log_name)
|
||||
shutil.make_archive(tempZipStorage, "zip", tempDir)
|
||||
|
||||
tempZipStorage += '.zip'
|
||||
websocket_helper.broadcast_user(exec_user['user_id'], 'send_logs_bootbox', {
|
||||
})
|
||||
|
||||
self.users.set_support_path(exec_user['user_id'], tempZipStorage)
|
||||
|
||||
@staticmethod
|
||||
def add_system_user():
|
||||
helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", helper_users.new_api_token(), False, False)
|
||||
|
||||
def get_server_settings(self, server_id):
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
@ -131,7 +190,7 @@ class Controller:
|
||||
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
return False
|
||||
|
||||
|
||||
def get_server_data(self, server_id):
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
@ -184,38 +243,10 @@ class Controller:
|
||||
console.info("All Servers Stopped")
|
||||
|
||||
def stop_server(self, server_id):
|
||||
# get object
|
||||
svr_obj = self.get_server_obj(server_id)
|
||||
svr_data = self.get_server_data(server_id)
|
||||
server_name = svr_data['server_name']
|
||||
|
||||
running = svr_obj.check_running()
|
||||
|
||||
# issue the stop command
|
||||
svr_obj = self.get_server_obj(server_id)
|
||||
svr_obj.stop_threaded_server()
|
||||
|
||||
# while it's running, we wait
|
||||
x = 0
|
||||
while running:
|
||||
logger.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
console.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
running = svr_obj.check_running()
|
||||
|
||||
# let's keep track of how long this is going on...
|
||||
x = x + 1
|
||||
|
||||
# if we have been waiting more than 120 seconds. let's just kill the pid
|
||||
if x >= 60:
|
||||
logger.error("Server {} is taking way too long to stop. Killing this process".format(server_name))
|
||||
console.error("Server {} is taking way too long to stop. Killing this process".format(server_name))
|
||||
|
||||
svr_obj.killpid(svr_obj.PID)
|
||||
running = False
|
||||
|
||||
# if we killed the server, let's clean up the object
|
||||
if not running:
|
||||
svr_obj.cleanup_server_object()
|
||||
|
||||
def create_jar_server(self, server: str, version: str, name: str, min_mem: int, max_mem: int, port: int):
|
||||
server_id = helper.create_uuid()
|
||||
server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
@ -231,7 +262,7 @@ class Controller:
|
||||
try:
|
||||
# do a eula.txt
|
||||
with open(os.path.join(server_dir, "eula.txt"), 'w') as f:
|
||||
f.write("eula=true")
|
||||
f.write("eula=false")
|
||||
f.close()
|
||||
|
||||
# setup server.properties with the port
|
||||
@ -252,11 +283,12 @@ class Controller:
|
||||
# download the jar
|
||||
server_jar_obj.download_jar(server, version, full_jar_path, name)
|
||||
|
||||
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop)
|
||||
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, port)
|
||||
return new_id
|
||||
|
||||
@staticmethod
|
||||
def verify_jar_server( server_path: str, server_jar: str):
|
||||
server_path = helper.get_os_understandable_path(server_path)
|
||||
path_check = helper.check_path_exists(server_path)
|
||||
jar_check = helper.check_file_exists(os.path.join(server_path, server_jar))
|
||||
if not path_check or not jar_check:
|
||||
@ -265,6 +297,7 @@ class Controller:
|
||||
|
||||
@staticmethod
|
||||
def verify_zip_server(zip_path: str):
|
||||
zip_path = helper.get_os_understandable_path(zip_path)
|
||||
zip_check = helper.check_file_exists(zip_path)
|
||||
if not zip_check:
|
||||
return False
|
||||
@ -277,8 +310,19 @@ class Controller:
|
||||
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
server_path = helper.get_os_understandable_path(server_path)
|
||||
dir_util.copy_tree(server_path, new_server_dir)
|
||||
|
||||
has_properties = False
|
||||
for item in os.listdir(new_server_dir):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
if not has_properties:
|
||||
logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port)))
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w") as f:
|
||||
f.write("server-port={}".format(port))
|
||||
f.close()
|
||||
|
||||
full_jar_path = os.path.join(new_server_dir, server_jar)
|
||||
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
|
||||
helper.float_to_string(max_mem),
|
||||
@ -294,41 +338,23 @@ class Controller:
|
||||
server_id = helper.create_uuid()
|
||||
new_server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
backup_path = os.path.join(helper.backup_path, server_id)
|
||||
|
||||
if helper.check_file_perms(zip_path):
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(tempDir)
|
||||
for i in range(len(zip_ref.filelist)):
|
||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
|
||||
test = zip_ref.filelist[i].filename
|
||||
break
|
||||
path_list = test.split('/')
|
||||
root_path = path_list[0]
|
||||
if len(path_list) > 1:
|
||||
for i in range(len(path_list)-2):
|
||||
root_path = os.path.join(root_path, path_list[i+1])
|
||||
|
||||
full_root_path = os.path.join(tempDir, root_path)
|
||||
|
||||
has_properties = False
|
||||
for item in os.listdir(full_root_path):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
try:
|
||||
shutil.move(os.path.join(full_root_path, item), os.path.join(new_server_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
|
||||
if not has_properties:
|
||||
logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port)))
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w") as f:
|
||||
f.write("server-port={}".format(port))
|
||||
f.close()
|
||||
zip_ref.close()
|
||||
else:
|
||||
return "false"
|
||||
tempDir = helper.get_os_understandable_path(zip_path)
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
has_properties = False
|
||||
#extracts archive to temp directory
|
||||
for item in os.listdir(tempDir):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
try:
|
||||
shutil.move(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
|
||||
if not has_properties:
|
||||
logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port)))
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w") as f:
|
||||
f.write("server-port={}".format(port))
|
||||
f.close()
|
||||
|
||||
full_jar_path = os.path.join(new_server_dir, server_jar)
|
||||
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
|
||||
@ -342,20 +368,33 @@ class Controller:
|
||||
server_log_file, server_stop, port)
|
||||
return new_id
|
||||
|
||||
def register_server(self, name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
def rename_backup_dir(self, old_server_id, new_server_id, new_uuid):
|
||||
server_data = self.servers.get_server_data_by_id(old_server_id)
|
||||
old_bu_path = server_data['backup_path']
|
||||
Server_Perms_Controller.backup_role_swap(old_server_id, new_server_id)
|
||||
backup_path = helper.validate_traversal(helper.backup_path, old_bu_path)
|
||||
backup_path_components = list(backup_path.parts)
|
||||
backup_path_components[-1] = new_uuid
|
||||
new_bu_path = pathlib.PurePath(os.path.join(*backup_path_components))
|
||||
if os.path.isdir(new_bu_path):
|
||||
if helper.validate_traversal(helper.backup_path, new_bu_path):
|
||||
os.rmdir(new_bu_path)
|
||||
backup_path.rename(new_bu_path)
|
||||
|
||||
def register_server(self, name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port: int):
|
||||
# put data in the db
|
||||
new_id = self.servers.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
|
||||
if not helper.check_file_exists(os.path.join(server_dir, "crafty_managed.txt")):
|
||||
try:
|
||||
# place a file in the dir saying it's owned by crafty
|
||||
with open(os.path.join(server_dir, "crafty_managed.txt"), 'w') as f:
|
||||
f.write(
|
||||
"The server is managed by Crafty Controller.\n Leave this directory/files alone please")
|
||||
f.close()
|
||||
|
||||
try:
|
||||
# place a file in the dir saying it's owned by crafty
|
||||
with open(os.path.join(server_dir, "crafty_managed.txt"), 'w') as f:
|
||||
f.write(
|
||||
"The server is managed by Crafty Controller.\n Leave this directory/files alone please")
|
||||
f.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to create required server files due to :{}".format(e))
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error("Unable to create required server files due to :{}".format(e))
|
||||
return False
|
||||
|
||||
# let's re-init all servers
|
||||
self.init_all_servers()
|
||||
@ -370,6 +409,7 @@ class Controller:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
server_data = self.get_server_data(server_id)
|
||||
server_name = server_data['server_name']
|
||||
backup_dir = self.servers.get_server_data_by_id(server_id)['backup_path']
|
||||
|
||||
logger.info("Deleting Server: ID {} | Name: {} ".format(server_id, server_name))
|
||||
console.info("Deleting Server: ID {} | Name: {} ".format(server_id, server_name))
|
||||
@ -380,7 +420,19 @@ class Controller:
|
||||
if running:
|
||||
self.stop_server(server_id)
|
||||
if files:
|
||||
shutil.rmtree(self.servers.get_server_data_by_id(server_id)['path'])
|
||||
try:
|
||||
shutil.rmtree(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['path']))
|
||||
except Exception as e:
|
||||
logger.error("Unable to delete server files for server with ID: {} with error logged: {}".format(server_id, e))
|
||||
if helper.check_path_exists(self.servers.get_server_data_by_id(server_id)['backup_path']):
|
||||
shutil.rmtree(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['backup_path']))
|
||||
|
||||
|
||||
#Cleanup scheduled tasks
|
||||
try:
|
||||
helpers_management.delete_scheduled_task_by_server(server_id)
|
||||
except DoesNotExist:
|
||||
logger.info("No scheduled jobs exist. Continuing.")
|
||||
# remove the server from the DB
|
||||
self.servers.remove_server(server_id)
|
||||
|
||||
@ -388,3 +440,4 @@ class Controller:
|
||||
self.servers_list.pop(counter)
|
||||
|
||||
counter += 1
|
||||
return
|
||||
|
@ -48,7 +48,7 @@ class db_builder:
|
||||
# Users.enabled: True,
|
||||
# Users.superuser: True
|
||||
#}).execute()
|
||||
user_id = users_helper.add_user(username=username, password=password, superuser=True)
|
||||
user_id = users_helper.add_user(username=username, password=password, email="default@example.com", superuser=True)
|
||||
#users_helper.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} )
|
||||
|
||||
#console.info("API token is {}".format(api_token))
|
||||
|
@ -3,31 +3,34 @@ import sys
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import psutil
|
||||
import pexpect
|
||||
import datetime
|
||||
import threading
|
||||
import schedule
|
||||
import logging.config
|
||||
import zipfile
|
||||
from threading import Thread
|
||||
import shutil
|
||||
import subprocess
|
||||
import zlib
|
||||
import html
|
||||
import apscheduler
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from tzlocal import get_localzone
|
||||
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.servers import Servers, servers_helper
|
||||
from app.classes.models.servers import Servers, helper_servers, servers_helper
|
||||
from app.classes.models.management import management_helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.shared.translation import translation
|
||||
from app.classes.models.users import users_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
try:
|
||||
import pexpect
|
||||
import psutil
|
||||
#import pexpect
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
@ -38,26 +41,43 @@ except ModuleNotFoundError as e:
|
||||
class ServerOutBuf:
|
||||
lines = {}
|
||||
|
||||
def __init__(self, p, server_id):
|
||||
self.p = p
|
||||
def __init__(self, proc, server_id):
|
||||
self.proc = proc
|
||||
self.server_id = str(server_id)
|
||||
# Buffers text for virtual_terminal_lines config number of lines
|
||||
self.max_lines = helper.get_setting('virtual_terminal_lines')
|
||||
self.line_buffer = ''
|
||||
ServerOutBuf.lines[self.server_id] = []
|
||||
self.lsi = 0
|
||||
|
||||
def process_byte(self, char):
|
||||
if char == os.linesep[self.lsi]:
|
||||
self.lsi += 1
|
||||
else:
|
||||
self.lsi = 0
|
||||
self.line_buffer += char
|
||||
|
||||
if self.lsi >= len(os.linesep):
|
||||
self.lsi = 0
|
||||
ServerOutBuf.lines[self.server_id].append(self.line_buffer)
|
||||
|
||||
self.new_line_handler(self.line_buffer)
|
||||
self.line_buffer = ''
|
||||
# Limit list length to self.max_lines:
|
||||
if len(ServerOutBuf.lines[self.server_id]) > self.max_lines:
|
||||
ServerOutBuf.lines[self.server_id].pop(0)
|
||||
|
||||
def check(self):
|
||||
while self.p.isalive():
|
||||
char = self.p.read(1)
|
||||
if char == os.linesep:
|
||||
ServerOutBuf.lines[self.server_id].append(self.line_buffer)
|
||||
self.new_line_handler(self.line_buffer)
|
||||
self.line_buffer = ''
|
||||
# Limit list length to self.max_lines:
|
||||
if len(ServerOutBuf.lines[self.server_id]) > self.max_lines:
|
||||
ServerOutBuf.lines[self.server_id].pop(0)
|
||||
while True:
|
||||
if self.proc.poll() is None:
|
||||
char = self.proc.stdout.read(1).decode('utf-8', 'ignore')
|
||||
# TODO: we may want to benchmark reading in blocks and userspace processing it later, reads are kind of expensive as a syscall
|
||||
self.process_byte(char)
|
||||
else:
|
||||
self.line_buffer += char
|
||||
flush = self.proc.stdout.read().decode('utf-8', 'ignore')
|
||||
for char in flush:
|
||||
self.process_byte(char)
|
||||
break
|
||||
|
||||
def new_line_handler(self, new_line):
|
||||
new_line = re.sub('(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )', '', new_line)
|
||||
@ -85,7 +105,6 @@ class Server:
|
||||
# holders for our process
|
||||
self.process = None
|
||||
self.line = False
|
||||
self.PID = None
|
||||
self.start_time = None
|
||||
self.server_command = None
|
||||
self.server_path = None
|
||||
@ -99,7 +118,7 @@ class Server:
|
||||
self.restart_count = 0
|
||||
self.crash_watcher_schedule = None
|
||||
self.stats = stats
|
||||
self.backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name="backup")
|
||||
self.backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name=f"backup_{self.name}")
|
||||
self.is_backingup = False
|
||||
|
||||
def reload_server_settings(self):
|
||||
@ -124,26 +143,31 @@ class Server:
|
||||
logger.info("Scheduling server {} to start in {} seconds".format(self.name, delay))
|
||||
console.info("Scheduling server {} to start in {} seconds".format(self.name, delay))
|
||||
|
||||
schedule.every(delay).seconds.do(self.run_scheduled_server)
|
||||
tz = get_localzone()
|
||||
self.server_scheduler = BackgroundScheduler(timezone=str(tz))
|
||||
self.server_scheduler.add_job(self.run_scheduled_server, 'interval', seconds=delay, id=str(self.server_id))
|
||||
self.server_scheduler.start()
|
||||
|
||||
def run_scheduled_server(self):
|
||||
console.info("Starting server ID: {} - {}".format(self.server_id, self.name))
|
||||
logger.info("Starting server {}".format(self.server_id, self.name))
|
||||
self.run_threaded_server()
|
||||
#Sets waiting start to false since we're attempting to start the server.
|
||||
servers_helper.set_waiting_start(self.server_id, False)
|
||||
self.run_threaded_server(None)
|
||||
|
||||
# remove the scheduled job since it's ran
|
||||
return schedule.CancelJob
|
||||
return self.server_scheduler.remove_job(str(self.server_id))
|
||||
|
||||
def run_threaded_server(self, lang):
|
||||
def run_threaded_server(self, user_id):
|
||||
# start the server
|
||||
self.server_thread = threading.Thread(target=self.start_server, daemon=True, args=(lang,), name='{}_server_thread'.format(self.server_id))
|
||||
self.server_thread = threading.Thread(target=self.start_server, daemon=True, args=(user_id,), name='{}_server_thread'.format(self.server_id))
|
||||
self.server_thread.start()
|
||||
|
||||
def setup_server_run_command(self):
|
||||
# configure the server
|
||||
server_exec_path = self.settings['executable']
|
||||
self.server_command = self.settings['execution_command']
|
||||
self.server_path = self.settings['path']
|
||||
server_exec_path = helper.get_os_understandable_path(self.settings['executable'])
|
||||
self.server_command = helper.cmdparse(self.settings['execution_command'])
|
||||
self.server_path = helper.get_os_understandable_path(self.settings['path'])
|
||||
|
||||
# let's do some quick checking to make sure things actually exists
|
||||
full_path = os.path.join(self.server_path, server_exec_path)
|
||||
@ -154,14 +178,16 @@ class Server:
|
||||
if not helper.check_path_exists(self.server_path):
|
||||
logger.critical("Server path: {} does not seem to exits".format(self.server_path))
|
||||
console.critical("Server path: {} does not seem to exits".format(self.server_path))
|
||||
helper.do_exit()
|
||||
|
||||
if not helper.check_writeable(self.server_path):
|
||||
logger.critical("Unable to write/access {}".format(self.server_path))
|
||||
console.warning("Unable to write/access {}".format(self.server_path))
|
||||
helper.do_exit()
|
||||
|
||||
def start_server(self, user_lang):
|
||||
def start_server(self, user_id):
|
||||
if not user_id:
|
||||
user_lang = helper.get_setting('language')
|
||||
else:
|
||||
user_lang = users_helper.get_user_lang_by_id(user_id)
|
||||
|
||||
logger.info("Start command detected. Reloading settings from DB for server {}".format(self.name))
|
||||
self.setup_server_run_command()
|
||||
@ -177,38 +203,68 @@ class Server:
|
||||
logger.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
console.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
|
||||
if os.name == "nt":
|
||||
logger.info("Windows Detected")
|
||||
self.server_command = self.server_command.replace('\\', '/')
|
||||
#Checks for eula. Creates one if none detected.
|
||||
#If EULA is detected and not set to one of these true vaiants we offer to set it true.
|
||||
if helper.check_file_exists(os.path.join(self.settings['path'], 'eula.txt')):
|
||||
f = open(os.path.join(self.settings['path'], 'eula.txt'), 'r')
|
||||
line = f.readline().lower()
|
||||
if line == 'eula=true':
|
||||
e_flag = True
|
||||
|
||||
elif line == 'eula = true':
|
||||
e_flag = True
|
||||
|
||||
elif line == 'eula= true':
|
||||
e_flag = True
|
||||
|
||||
elif line == 'eula =true':
|
||||
e_flag = True
|
||||
|
||||
else:
|
||||
e_flag = False
|
||||
else:
|
||||
logger.info("Linux Detected")
|
||||
e_flag = False
|
||||
|
||||
if e_flag == False:
|
||||
if user_id:
|
||||
websocket_helper.broadcast_user(user_id, 'send_eula_bootbox', {
|
||||
'id': self.server_id
|
||||
})
|
||||
else:
|
||||
logger.error("Autostart failed due to EULA being false. Agree not sent due to auto start.")
|
||||
return False
|
||||
return False
|
||||
f.close()
|
||||
if helper.is_os_windows():
|
||||
logger.info("Windows Detected")
|
||||
creationflags=subprocess.CREATE_NEW_CONSOLE
|
||||
else:
|
||||
logger.info("Unix Detected")
|
||||
creationflags=None
|
||||
|
||||
logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command))
|
||||
|
||||
servers_helper.set_waiting_start(self.server_id, False)
|
||||
try:
|
||||
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding='utf-8')
|
||||
out_buf = ServerOutBuf(self.process, self.server_id)
|
||||
self.process = subprocess.Popen(self.server_command, cwd=self.server_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
except Exception as ex:
|
||||
msg = "Server {} failed to start with error code: {}".format(self.name, ex)
|
||||
logger.error(msg)
|
||||
websocket_helper.broadcast('send_start_error', {
|
||||
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
|
||||
})
|
||||
return False
|
||||
if helper.check_internet():
|
||||
loc_server_port = servers_helper.get_server_stats_by_id(self.server_id)['server_port']
|
||||
if helper.check_port(loc_server_port):
|
||||
websocket_helper.broadcast('send_start_reload', {
|
||||
#Checks for java on initial fail
|
||||
if os.system("java -version") == 32512:
|
||||
msg = "Server {} failed to start with error code: {}".format(self.name, "Java not found. Please install Java then try again.")
|
||||
if user_id:
|
||||
websocket_helper.broadcast_user(user_id, 'send_start_error',{
|
||||
'error': translation.translate('error', 'noJava', user_lang).format(self.name)
|
||||
})
|
||||
return False
|
||||
else:
|
||||
websocket_helper.broadcast('send_start_error', {
|
||||
'error': translation.translate('error', 'closedPort', user_lang).format(loc_server_port)
|
||||
})
|
||||
else:
|
||||
websocket_helper.broadcast('send_start_error', {
|
||||
'error': translation.translate('error', 'internet', user_lang)
|
||||
})
|
||||
msg = "Server {} failed to start with error code: {}".format(self.name, ex)
|
||||
logger.error(msg)
|
||||
if user_id:
|
||||
websocket_helper.broadcast_user(user_id, 'send_start_error',{
|
||||
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
|
||||
})
|
||||
return False
|
||||
|
||||
out_buf = ServerOutBuf(self.process, self.server_id)
|
||||
|
||||
logger.debug('Starting virtual terminal listener for server {}'.format(self.name))
|
||||
threading.Thread(target=out_buf.check, daemon=True, name='{}_virtual_terminal'.format(self.server_id)).start()
|
||||
@ -217,21 +273,41 @@ class Server:
|
||||
|
||||
self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
|
||||
|
||||
if psutil.pid_exists(self.process.pid):
|
||||
self.PID = self.process.pid
|
||||
logger.info("Server {} running with PID {}".format(self.name, self.PID))
|
||||
console.info("Server {} running with PID {}".format(self.name, self.PID))
|
||||
if self.process.poll() is None:
|
||||
logger.info("Server {} running with PID {}".format(self.name, self.process.pid))
|
||||
console.info("Server {} running with PID {}".format(self.name, self.process.pid))
|
||||
self.is_crashed = False
|
||||
self.stats.record_stats()
|
||||
check_internet_thread = threading.Thread(target=self.check_internet_thread, daemon=True, args=(user_id, user_lang, ), name="{self.name}_Internet")
|
||||
check_internet_thread.start()
|
||||
#Checks if this is the servers first run.
|
||||
if servers_helper.get_first_run(self.server_id):
|
||||
servers_helper.set_first_run(self.server_id)
|
||||
loc_server_port = servers_helper.get_server_stats_by_id(self.server_id)['server_port']
|
||||
#Sends port reminder message.
|
||||
websocket_helper.broadcast_user(user_id, 'send_start_error', {
|
||||
'error': translation.translate('error', 'portReminder', user_lang).format(self.name, loc_server_port)
|
||||
})
|
||||
else:
|
||||
websocket_helper.broadcast_user(user_id, 'send_start_reload', {
|
||||
})
|
||||
else:
|
||||
logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.PID))
|
||||
console.warning("Server PID {} died right after starting - is this a server config issue?".format(self.PID))
|
||||
logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid))
|
||||
console.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid))
|
||||
|
||||
if self.settings['crash_detection']:
|
||||
logger.info("Server {} has crash detection enabled - starting watcher task".format(self.name))
|
||||
console.info("Server {} has crash detection enabled - starting watcher task".format(self.name))
|
||||
|
||||
self.crash_watcher_schedule = schedule.every(30).seconds.do(self.detect_crash).tag(self.name)
|
||||
|
||||
def check_internet_thread(self, user_id, user_lang):
|
||||
if user_id:
|
||||
if not helper.check_internet():
|
||||
websocket_helper.broadcast_user(user_id, 'send_start_error', {
|
||||
'error': translation.translate('error', 'internet', user_lang)
|
||||
})
|
||||
return
|
||||
|
||||
def stop_threaded_server(self):
|
||||
self.stop_server()
|
||||
@ -242,51 +318,52 @@ class Server:
|
||||
def stop_server(self):
|
||||
if self.settings['stop_command']:
|
||||
self.send_command(self.settings['stop_command'])
|
||||
|
||||
running = self.check_running()
|
||||
x = 0
|
||||
|
||||
# caching the name and pid number
|
||||
server_name = self.name
|
||||
server_pid = self.PID
|
||||
|
||||
while running:
|
||||
x = x+1
|
||||
logger.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
console.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
console.info("Server has {} seconds to respond before we force it down".format(int(60-(x*2))))
|
||||
running = self.check_running()
|
||||
time.sleep(2)
|
||||
|
||||
# if we haven't closed in 60 seconds, let's just slam down on the PID
|
||||
if x >= 30:
|
||||
logger.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
console.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
self.process.terminate(force=True)
|
||||
|
||||
logger.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
console.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
|
||||
else:
|
||||
self.process.terminate(force=True)
|
||||
#windows will need to be handled separately for Ctrl+C
|
||||
self.process.terminate()
|
||||
running = self.check_running()
|
||||
if not running:
|
||||
logger.info("Can't stop server {} if it's not running".format(self.name))
|
||||
console.info("Can't stop server {} if it's not running".format(self.name))
|
||||
return
|
||||
x = 0
|
||||
|
||||
# caching the name and pid number
|
||||
server_name = self.name
|
||||
server_pid = self.process.pid
|
||||
|
||||
while running:
|
||||
x = x+1
|
||||
logstr = "Server {} is still running - waiting 2s to see if it stops ({} seconds until force close)".format(server_name, int(60-(x*2)))
|
||||
logger.info(logstr)
|
||||
console.info(logstr)
|
||||
running = self.check_running()
|
||||
time.sleep(2)
|
||||
|
||||
# if we haven't closed in 60 seconds, let's just slam down on the PID
|
||||
if x >= 30:
|
||||
logger.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
console.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
self.kill()
|
||||
|
||||
logger.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
console.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
|
||||
# massive resetting of variables
|
||||
self.cleanup_server_object()
|
||||
|
||||
self.stats.record_stats()
|
||||
|
||||
def restart_threaded_server(self, lang):
|
||||
|
||||
def restart_threaded_server(self, user_id):
|
||||
# if not already running, let's just start
|
||||
if not self.check_running():
|
||||
self.run_threaded_server(lang)
|
||||
self.run_threaded_server(user_id)
|
||||
else:
|
||||
self.stop_threaded_server()
|
||||
time.sleep(2)
|
||||
self.run_threaded_server(lang)
|
||||
self.run_threaded_server(user_id)
|
||||
|
||||
def cleanup_server_object(self):
|
||||
self.PID = None
|
||||
self.start_time = None
|
||||
self.restart_count = 0
|
||||
self.is_crashed = False
|
||||
@ -294,35 +371,27 @@ class Server:
|
||||
self.process = None
|
||||
|
||||
def check_running(self):
|
||||
running = False
|
||||
# if process is None, we never tried to start
|
||||
if self.PID is None:
|
||||
return running
|
||||
|
||||
try:
|
||||
alive = self.process.isalive()
|
||||
if type(alive) is not bool:
|
||||
self.last_rc = alive
|
||||
running = False
|
||||
else:
|
||||
running = alive
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to find if server PID exists: {}".format(self.PID), exc_info=True)
|
||||
pass
|
||||
|
||||
return running
|
||||
if self.process is None:
|
||||
return False
|
||||
poll = self.process.poll()
|
||||
if poll is None:
|
||||
return True
|
||||
else:
|
||||
self.last_rc = poll
|
||||
return False
|
||||
|
||||
def send_command(self, command):
|
||||
|
||||
console.info("COMMAND TIME: {}".format(command))
|
||||
if not self.check_running() and command.lower() != 'start':
|
||||
logger.warning("Server not running, unable to send command \"{}\"".format(command))
|
||||
return False
|
||||
|
||||
logger.debug("Sending command {} to server via pexpect".format(command))
|
||||
logger.debug("Sending command {} to server".format(command))
|
||||
|
||||
# send it
|
||||
self.process.send(command + '\n')
|
||||
self.process.stdin.write("{}\n".format(command).encode('utf-8'))
|
||||
self.process.stdin.flush()
|
||||
|
||||
def crash_detected(self, name):
|
||||
|
||||
@ -335,7 +404,7 @@ class Server:
|
||||
if self.settings['crash_detection']:
|
||||
logger.warning("The server {} has crashed and will be restarted. Restarting server".format(name))
|
||||
console.warning("The server {} has crashed and will be restarted. Restarting server".format(name))
|
||||
self.run_threaded_server()
|
||||
self.run_threaded_server(None)
|
||||
return True
|
||||
else:
|
||||
logger.critical(
|
||||
@ -344,18 +413,18 @@ class Server:
|
||||
"The server {} has crashed, crash detection is disabled and it will not be restarted".format(name))
|
||||
return False
|
||||
|
||||
def killpid(self, pid):
|
||||
logger.info("Terminating PID {} and all child processes".format(pid))
|
||||
process = psutil.Process(pid)
|
||||
def kill(self):
|
||||
logger.info("Terminating server {} and all child processes".format(self.server_id))
|
||||
process = psutil.Process(self.process.pid)
|
||||
|
||||
# for every sub process...
|
||||
for proc in process.children(recursive=True):
|
||||
# kill all the child processes - it sounds too wrong saying kill all the children (kevdagoat: lol!)
|
||||
logger.info("Sending SIGKILL to PID {}".format(proc.name))
|
||||
logger.info("Sending SIGKILL to server {}".format(proc.name))
|
||||
proc.kill()
|
||||
# kill the main process we are after
|
||||
logger.info('Sending SIGKILL to parent')
|
||||
process.kill()
|
||||
self.process.kill()
|
||||
|
||||
def get_start_time(self):
|
||||
if self.check_running():
|
||||
@ -364,8 +433,11 @@ class Server:
|
||||
return False
|
||||
|
||||
def get_pid(self):
|
||||
return self.PID
|
||||
|
||||
if self.process is not None:
|
||||
return self.process.pid
|
||||
else:
|
||||
return None
|
||||
|
||||
def detect_crash(self):
|
||||
|
||||
logger.info("Detecting possible crash for server: {} ".format(self.name))
|
||||
@ -406,6 +478,13 @@ class Server:
|
||||
console.info("Removing old crash detection watcher thread")
|
||||
schedule.clear(self.name)
|
||||
|
||||
def agree_eula(self, user_id):
|
||||
file = os.path.join(self.server_path, 'eula.txt')
|
||||
f = open(file, 'w')
|
||||
f.write('eula=true')
|
||||
f.close
|
||||
self.run_threaded_server(user_id)
|
||||
|
||||
def is_backup_running(self):
|
||||
if self.is_backingup:
|
||||
return True
|
||||
@ -413,8 +492,11 @@ class Server:
|
||||
return False
|
||||
|
||||
def backup_server(self):
|
||||
backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name="backup")
|
||||
backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name=f"backup_{self.name}")
|
||||
logger.info("Starting Backup Thread for server {}.".format(self.settings['server_name']))
|
||||
if self.server_path == None:
|
||||
self.server_path = helper.get_os_understandable_path(self.settings['path'])
|
||||
logger.info("Backup Thread - Local server path not defined. Setting local server path variable.")
|
||||
#checks if the backup thread is currently alive for this server
|
||||
if not self.is_backingup:
|
||||
try:
|
||||
@ -434,13 +516,13 @@ class Server:
|
||||
try:
|
||||
backup_filename = "{}/{}".format(self.settings['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
logger.info("Creating backup of server '{}' (ID#{}) at '{}'".format(self.settings['server_name'], self.server_id, backup_filename))
|
||||
shutil.make_archive(backup_filename, 'zip', self.server_path)
|
||||
shutil.make_archive(helper.get_os_understandable_path(backup_filename), 'zip', self.server_path)
|
||||
while len(self.list_backups()) > conf["max_backups"] and conf["max_backups"] > 0:
|
||||
backup_list = self.list_backups()
|
||||
oldfile = backup_list[0]
|
||||
oldfile_path = "{}/{}".format(conf['backup_path'], oldfile['path'])
|
||||
logger.info("Removing old backup '{}'".format(oldfile['path']))
|
||||
os.remove(oldfile_path)
|
||||
os.remove(helper.get_os_understandable_path(oldfile_path))
|
||||
self.is_backingup = False
|
||||
logger.info("Backup of server: {} completed".format(self.name))
|
||||
return
|
||||
@ -450,21 +532,23 @@ class Server:
|
||||
return
|
||||
|
||||
def list_backups(self):
|
||||
conf = management_helper.get_backup_config(self.server_id)
|
||||
if helper.check_path_exists(self.settings['backup_path']):
|
||||
files = helper.get_human_readable_files_sizes(helper.list_dir_by_date(self.settings['backup_path']))
|
||||
return [{"path": os.path.relpath(f['path'], start=conf['backup_path']), "size": f["size"]} for f in files]
|
||||
if self.settings['backup_path']:
|
||||
if helper.check_path_exists(helper.get_os_understandable_path(self.settings['backup_path'])):
|
||||
files = helper.get_human_readable_files_sizes(helper.list_dir_by_date(helper.get_os_understandable_path(self.settings['backup_path'])))
|
||||
return [{"path": os.path.relpath(f['path'], start=helper.get_os_understandable_path(self.settings['backup_path'])), "size": f["size"]} for f in files]
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
return []
|
||||
logger.info("Error putting backup file list for server with ID: {}".format(self.server_id))
|
||||
return[]
|
||||
|
||||
def jar_update(self):
|
||||
servers_helper.set_update(self.server_id, True)
|
||||
update_thread = threading.Thread(target=self.a_jar_update, daemon=True, name="exe_update")
|
||||
update_thread = threading.Thread(target=self.a_jar_update, daemon=True, name=f"exe_update_{self.name}")
|
||||
update_thread.start()
|
||||
|
||||
def check_update(self):
|
||||
server_stats = servers_helper.get_server_stats_by_id(self.server_id)
|
||||
if server_stats['updating']:
|
||||
if servers_helper.get_update_status(self.server_id):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -476,7 +560,7 @@ class Server:
|
||||
#checks if server is running. Calls shutdown if it is running.
|
||||
if self.check_running():
|
||||
wasStarted = True
|
||||
logger.info("Server with PID {} is running. Sending shutdown command".format(self.PID))
|
||||
logger.info("Server with PID {} is running. Sending shutdown command".format(self.process.pid))
|
||||
self.stop_threaded_server()
|
||||
else:
|
||||
wasStarted = False
|
||||
@ -484,13 +568,15 @@ class Server:
|
||||
# There are clients
|
||||
self.check_update()
|
||||
message = '<a data-id="'+str(self.server_id)+'" class=""> UPDATING...</i></a>'
|
||||
websocket_helper.broadcast('update_button_status', {
|
||||
websocket_helper.broadcast_page('/panel/server_detail', 'update_button_status', {
|
||||
'isUpdating': self.check_update(),
|
||||
'server_id': self.server_id,
|
||||
'wasRunning': wasStarted,
|
||||
'string': message
|
||||
})
|
||||
backup_dir = os.path.join(self.settings['path'], 'crafty_executable_backups')
|
||||
websocket_helper.broadcast_page('/panel/dashboard', 'send_start_reload', {
|
||||
})
|
||||
backup_dir = os.path.join(helper.get_os_understandable_path(self.settings['path']), 'crafty_executable_backups')
|
||||
#checks if backup directory already exists
|
||||
if os.path.isdir(backup_dir):
|
||||
backup_executable = os.path.join(backup_dir, 'old_server.jar')
|
||||
@ -507,7 +593,7 @@ class Server:
|
||||
else:
|
||||
logger.info("No old backups found for server: {}".format(self.name))
|
||||
|
||||
current_executable = os.path.join(self.settings['path'], self.settings['executable'])
|
||||
current_executable = os.path.join(helper.get_os_understandable_path(self.settings['path']), self.settings['executable'])
|
||||
|
||||
#copies to backup dir
|
||||
helper.copy_files(current_executable, backup_executable)
|
||||
@ -517,7 +603,6 @@ class Server:
|
||||
|
||||
while servers_helper.get_server_stats_by_id(self.server_id)['updating']:
|
||||
if downloaded and not self.is_backingup:
|
||||
print("Backup Status: " + str(self.is_backingup))
|
||||
logger.info("Executable updated successfully. Starting Server")
|
||||
|
||||
servers_helper.set_update(self.server_id, False)
|
||||
@ -526,11 +611,13 @@ class Server:
|
||||
self.check_update()
|
||||
websocket_helper.broadcast('notification', "Executable update finished for " + self.name)
|
||||
time.sleep(3)
|
||||
websocket_helper.broadcast('update_button_status', {
|
||||
websocket_helper.broadcast_page('/panel/server_detail', 'update_button_status', {
|
||||
'isUpdating': self.check_update(),
|
||||
'server_id': self.server_id,
|
||||
'wasRunning': wasStarted
|
||||
})
|
||||
websocket_helper.broadcast_page('/panel/dashboard', 'send_start_reload', {
|
||||
})
|
||||
websocket_helper.broadcast('notification', "Executable update finished for "+self.name)
|
||||
|
||||
management_helper.add_to_audit_log_raw('Alert', '-1', self.server_id, "Executable update finished for "+self.name, self.settings['server_ip'])
|
||||
|
@ -1,3 +1,4 @@
|
||||
from datetime import timedelta
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
@ -6,6 +7,9 @@ import logging
|
||||
import threading
|
||||
import asyncio
|
||||
import shutil
|
||||
from tzlocal import get_localzone
|
||||
|
||||
from app.classes.controllers.users_controller import Users_Controller
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
@ -15,11 +19,12 @@ from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.models.servers import servers_helper
|
||||
from app.classes.models.management import management_helper
|
||||
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED, EVENT_ALL, EVENT_JOB_REMOVED
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger('apscheduler')
|
||||
|
||||
try:
|
||||
import schedule
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
@ -46,9 +51,13 @@ class TasksManager:
|
||||
self.controller = controller
|
||||
self.tornado = Webserver(controller, self)
|
||||
|
||||
tz = get_localzone()
|
||||
self.scheduler = BackgroundScheduler(timezone=str(tz))
|
||||
|
||||
self.users_controller = Users_Controller()
|
||||
|
||||
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread')
|
||||
|
||||
self.main_kill_switch_thread = threading.Thread(target=self.main_kill_switch, daemon=True, name="main_loop")
|
||||
self.main_thread_exiting = False
|
||||
|
||||
self.schedule_thread = threading.Thread(target=self.scheduler_thread, daemon=True, name="scheduler")
|
||||
@ -65,31 +74,11 @@ class TasksManager:
|
||||
def get_main_thread_run_status(self):
|
||||
return self.main_thread_exiting
|
||||
|
||||
def start_main_kill_switch_watcher(self):
|
||||
self.main_kill_switch_thread.start()
|
||||
|
||||
def main_kill_switch(self):
|
||||
while True:
|
||||
if os.path.exists(os.path.join(helper.root_dir, 'exit.txt')):
|
||||
logger.info("Found Exit File, stopping everything")
|
||||
self._main_graceful_exit()
|
||||
time.sleep(5)
|
||||
|
||||
def reload_schedule_from_db(self):
|
||||
jobs = management_helper.get_schedules_enabled()
|
||||
schedule.clear(tag='backup')
|
||||
schedule.clear(tag='db')
|
||||
for j in jobs:
|
||||
if j.interval_type in scheduler_intervals:
|
||||
logger.info("Loading schedule ID#{i}: '{a}' every {n} {t} at {s}".format(
|
||||
i=j.schedule_id, a=j.action, n=j.interval, t=j.interval_type, s=j.start_time))
|
||||
try:
|
||||
getattr(schedule.every(j.interval), j.interval_type).at(j.start_time).do(
|
||||
management_helper.send_command, 0, j.server_id, "127.27.23.89", j.action)
|
||||
except schedule.ScheduleValueError as e:
|
||||
logger.critical("Scheduler value error occurred: {} on ID#{}".format(e, j.schedule_id))
|
||||
else:
|
||||
logger.critical("Unknown schedule job type '{}' at id {}, skipping".format(j.interval_type, j.schedule_id))
|
||||
logger.info("Reload from DB called. Current enabled schedules: ")
|
||||
for item in jobs:
|
||||
logger.info("JOB: {}".format(item))
|
||||
|
||||
def command_watcher(self):
|
||||
while True:
|
||||
@ -98,23 +87,25 @@ class TasksManager:
|
||||
for c in commands:
|
||||
|
||||
svr = self.controller.get_server_obj(c['server_id']['server_id'])
|
||||
user_lang = c.get('user')['lang']
|
||||
user_id = c.get('user')['user_id']
|
||||
command = c.get('command', None)
|
||||
|
||||
if command == 'start_server':
|
||||
svr.run_threaded_server(user_lang)
|
||||
svr.run_threaded_server(user_id)
|
||||
|
||||
elif command == 'stop_server':
|
||||
svr.stop_threaded_server()
|
||||
|
||||
elif command == "restart_server":
|
||||
svr.restart_threaded_server(user_lang)
|
||||
svr.restart_threaded_server(user_id)
|
||||
|
||||
elif command == "backup_server":
|
||||
svr.backup_server()
|
||||
|
||||
elif command == "update_executable":
|
||||
svr.jar_update(user_lang)
|
||||
svr.jar_update()
|
||||
else:
|
||||
svr.send_command(command)
|
||||
management_helper.mark_command_complete(c.get('command_id', None))
|
||||
|
||||
time.sleep(1)
|
||||
@ -122,7 +113,6 @@ class TasksManager:
|
||||
def _main_graceful_exit(self):
|
||||
try:
|
||||
os.remove(helper.session_file)
|
||||
os.remove(os.path.join(helper.root_dir, 'exit.txt'))
|
||||
os.remove(os.path.join(helper.root_dir, '.header'))
|
||||
self.controller.stop_all_servers()
|
||||
except:
|
||||
@ -159,11 +149,102 @@ class TasksManager:
|
||||
console.info("Launching realtime thread...")
|
||||
self.realtime_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def scheduler_thread():
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
def scheduler_thread(self):
|
||||
schedules = management_helper.get_schedules_enabled()
|
||||
self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
|
||||
#self.scheduler.add_job(self.scheduler.print_jobs, 'interval', seconds=10, id='-1')
|
||||
#load schedules from DB
|
||||
for schedule in schedules:
|
||||
if schedule.cron_string != "":
|
||||
cron = schedule.cron_string.split(' ')
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
|
||||
else:
|
||||
if schedule.interval_type == 'hours':
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = 0, hour = '*/'+str(schedule.interval), id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
|
||||
elif schedule.interval_type == 'minutes':
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = '*/'+str(schedule.interval), id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
|
||||
elif schedule.interval_type == 'days':
|
||||
time = schedule.start_time.split(':')
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', day = '*/'+str(schedule.interval), hour=time[0], minute=time[1], id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
|
||||
|
||||
self.scheduler.start()
|
||||
jobs = self.scheduler.get_jobs()
|
||||
logger.info("Loaded schedules. Current enabled schedules: ")
|
||||
for item in jobs:
|
||||
logger.info("JOB: {}".format(item))
|
||||
|
||||
def schedule_job(self, job_data):
|
||||
sch_id = management_helper.create_scheduled_task(job_data['server_id'], job_data['action'], job_data['interval'], job_data['interval_type'], job_data['start_time'], job_data['command'], "None", job_data['enabled'], job_data['one_time'], job_data['cron_string'])
|
||||
if job_data['enabled']:
|
||||
if job_data['cron_string'] != "":
|
||||
cron = job_data['cron_string'].split(' ')
|
||||
try:
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
|
||||
except Exception as e:
|
||||
console.error("Failed to schedule task with error: {}.".format(e))
|
||||
console.info("Removing failed task from DB.")
|
||||
management_helper.delete_scheduled_task(sch_id)
|
||||
else:
|
||||
if job_data['interval_type'] == 'hours':
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = 0, hour = '*/'+str(job_data['interval']), id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
|
||||
elif job_data['interval_type'] == 'minutes':
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = '*/'+str(job_data['interval']), id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
|
||||
elif job_data['interval_type'] == 'days':
|
||||
time = job_data['start_time'].split(':')
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', day = '*/'+str(job_data['interval']), hour = time[0], minute = time[1], id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']], )
|
||||
logger.info("Added job. Current enabled schedules: ")
|
||||
jobs = self.scheduler.get_jobs()
|
||||
for item in jobs:
|
||||
logger.info("JOB: {}".format(item))
|
||||
|
||||
def remove_job(self, sch_id):
|
||||
job = management_helper.get_scheduled_task_model(sch_id)
|
||||
management_helper.delete_scheduled_task(sch_id)
|
||||
if job.enabled:
|
||||
self.scheduler.remove_job(str(sch_id))
|
||||
else:
|
||||
logger.info("Job with ID {} was deleted from DB, but was not enabled. Not going to try removing something that doesn't exist from active schedules.")
|
||||
|
||||
def update_job(self, sch_id, job_data):
|
||||
management_helper.update_scheduled_task(sch_id, job_data)
|
||||
try:
|
||||
self.scheduler.remove_job(str(sch_id))
|
||||
except:
|
||||
logger.info("No job found in update job. Assuming it was previously disabled. Starting new job.")
|
||||
if job_data['cron_string'] != "":
|
||||
cron = job_data['cron_string'].split(' ')
|
||||
try:
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
|
||||
except Exception as e:
|
||||
console.error("Failed to schedule task with error: {}.".format(e))
|
||||
console.info("Removing failed task from DB.")
|
||||
management_helper.delete_scheduled_task(sch_id)
|
||||
else:
|
||||
if job_data['interval_type'] == 'hours':
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = 0, hour = '*/'+str(job_data['interval']), id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
|
||||
elif job_data['interval_type'] == 'minutes':
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', minute = '*/'+str(job_data['interval']), id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
|
||||
elif job_data['interval_type'] == 'days':
|
||||
time = job_data['start_time'].split(':')
|
||||
self.scheduler.add_job(management_helper.add_command, 'cron', day = '*/'+str(job_data['interval']), hour = time[0], minute = time[1], id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']], )
|
||||
else:
|
||||
try:
|
||||
self.scheduler.get_job(str(sch_id))
|
||||
self.scheduler.remove_job(str(sch_id))
|
||||
except:
|
||||
logger.info("APScheduler found no scheduled job on schedule update for schedule with id: {}. Assuming it was already disabled.".format(sch_id))
|
||||
|
||||
def schedule_watcher(self, event):
|
||||
if not event.exception:
|
||||
if str(event.job_id).isnumeric():
|
||||
task = management_helper.get_scheduled_task_model(int(event.job_id))
|
||||
if task.one_time:
|
||||
self.remove_job(task.schedule_id)
|
||||
logger.info("one time task detected. Deleting...")
|
||||
else:
|
||||
logger.info("Event job ID is not numerical. Assuming it's stats - not stored in DB. Moving on.")
|
||||
else:
|
||||
logger.error("Task failed with error: {}".format(event.exception))
|
||||
|
||||
def start_stats_recording(self):
|
||||
stats_update_frequency = helper.get_setting('stats_update_frequency')
|
||||
@ -172,17 +253,16 @@ class TasksManager:
|
||||
|
||||
# one for now,
|
||||
self.controller.stats.record_stats()
|
||||
|
||||
# one for later
|
||||
schedule.every(stats_update_frequency).seconds.do(self.controller.stats.record_stats).tag('stats-recording')
|
||||
self.scheduler.add_job(self.controller.stats.record_stats, 'interval', seconds=stats_update_frequency, id="stats")
|
||||
|
||||
@staticmethod
|
||||
def serverjar_cache_refresher():
|
||||
|
||||
def serverjar_cache_refresher(self):
|
||||
logger.info("Refreshing serverjars.com cache on start")
|
||||
server_jar_obj.refresh_cache()
|
||||
|
||||
logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
|
||||
schedule.every(12).hours.do(server_jar_obj.refresh_cache).tag('serverjars')
|
||||
self.scheduler.add_job(server_jar_obj.refresh_cache, 'interval', hours=12, id="serverjars")
|
||||
|
||||
@staticmethod
|
||||
def realtime():
|
||||
@ -214,5 +294,5 @@ class TasksManager:
|
||||
|
||||
def log_watcher(self):
|
||||
self.controller.servers.check_for_old_logs()
|
||||
schedule.every(6).hours.do(lambda: self.controller.servers.check_for_old_logs()).tag('log-mgmt')
|
||||
self.scheduler.add_job(self.controller.servers.check_for_old_logs, 'interval', hours=6, id="log-mgmt")
|
||||
|
||||
|
@ -14,6 +14,7 @@ class Translation():
|
||||
self.cached_translation = None
|
||||
self.cached_translation_lang = None
|
||||
self.lang_file_exists = []
|
||||
|
||||
def translate(self, page, word, lang):
|
||||
translated_word = None
|
||||
fallback_lang = 'en_EN'
|
||||
@ -30,6 +31,7 @@ class Translation():
|
||||
elif iter(translated_word) and not isinstance(translated_word, str): return '\n'.join(translated_word)
|
||||
return translated_word
|
||||
return 'Error while getting translation'
|
||||
|
||||
def translate_inner(self, page, word, lang):
|
||||
lang_file = os.path.join(
|
||||
self.translations_path,
|
||||
|
@ -12,12 +12,14 @@ import os
|
||||
import shutil
|
||||
import html
|
||||
import re
|
||||
from app.classes.models.users import helper_users
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import Users, installer
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.server import ServerOutBuf
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -69,7 +71,7 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
if full_log:
|
||||
log_lines = helper.get_setting('max_log_lines')
|
||||
data = helper.tail_file(server_data['log_path'], log_lines)
|
||||
data = helper.tail_file(helper.get_os_understandable_path(server_data['log_path']), log_lines)
|
||||
else:
|
||||
data = ServerOutBuf.lines.get(server_id, [])
|
||||
|
||||
@ -92,21 +94,21 @@ class AjaxHandler(BaseHandler):
|
||||
self.render_page('ajax/notify.html', page_data)
|
||||
|
||||
elif page == "get_file":
|
||||
file_path = self.get_argument('file_path', None)
|
||||
file_path = helper.get_os_understandable_path(self.get_argument('file_path', None))
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in get_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in get_file ajax call ({})".format(file_path))
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
error = None
|
||||
|
||||
|
||||
try:
|
||||
with open(file_path) as file:
|
||||
file_contents = file.read()
|
||||
@ -122,17 +124,61 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
elif page == "get_tree":
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_tree'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
self.write(self.controller.servers.get_server_data_by_id(server_id)['path'] + '\n' +
|
||||
helper.generate_tree(self.controller.servers.get_server_data_by_id(server_id)['path']))
|
||||
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_tree(path))
|
||||
self.finish()
|
||||
|
||||
elif page == "get_zip_tree":
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_zip_tree(path))
|
||||
self.finish()
|
||||
|
||||
elif page == "get_zip_dir":
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_zip_dir(path))
|
||||
self.finish()
|
||||
|
||||
elif page == "get_dir":
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_tree'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_dir(path))
|
||||
self.finish()
|
||||
|
||||
@tornado.web.authenticated
|
||||
def post(self, page):
|
||||
user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
server_id = self.get_argument('id', None)
|
||||
exec_user_id = user_data['user_id']
|
||||
exec_user = helper_users.get_user(exec_user_id)
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
error = bleach.clean(self.get_argument('error', "WTF Error!"))
|
||||
|
||||
page_data = {
|
||||
@ -157,7 +203,11 @@ class AjaxHandler(BaseHandler):
|
||||
self.controller.management.add_to_audit_log(user_data['user_id'], "Sent command to {} terminal: {}".format(self.controller.servers.get_server_friendly_name(server_id), command), server_id, self.get_remote_ip())
|
||||
|
||||
elif page == "create_file":
|
||||
file_parent = self.get_body_argument('file_parent', default=None, strip=True)
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
file_parent = helper.get_os_understandable_path(self.get_body_argument('file_parent', default=None, strip=True))
|
||||
file_name = self.get_body_argument('file_name', default=None, strip=True)
|
||||
file_path = os.path.join(file_parent, file_name)
|
||||
server_id = self.get_argument('id', None)
|
||||
@ -165,7 +215,7 @@ class AjaxHandler(BaseHandler):
|
||||
if not self.check_server_id(server_id, 'create_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path) \
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), file_path) \
|
||||
or helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in create_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in create_file ajax call ({})".format(file_path))
|
||||
@ -176,7 +226,11 @@ class AjaxHandler(BaseHandler):
|
||||
file_object.close()
|
||||
|
||||
elif page == "create_dir":
|
||||
dir_parent = self.get_body_argument('dir_parent', default=None, strip=True)
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
dir_parent = helper.get_os_understandable_path(self.get_body_argument('dir_parent', default=None, strip=True))
|
||||
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
|
||||
dir_path = os.path.join(dir_parent, dir_name)
|
||||
server_id = self.get_argument('id', None)
|
||||
@ -184,7 +238,7 @@ class AjaxHandler(BaseHandler):
|
||||
if not self.check_server_id(server_id, 'create_dir'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], dir_path) \
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), dir_path) \
|
||||
or helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
|
||||
console.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
|
||||
@ -193,38 +247,92 @@ class AjaxHandler(BaseHandler):
|
||||
os.mkdir(dir_path)
|
||||
|
||||
elif page == "unzip_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
path = helper.get_os_understandable_path(self.get_argument('path', None))
|
||||
helper.unzipFile(path)
|
||||
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
|
||||
return
|
||||
|
||||
elif page == "kill":
|
||||
if not permissions['Commands'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Commands")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
svr = self.controller.get_server_obj(server_id)
|
||||
if svr.get_pid():
|
||||
try:
|
||||
svr.killpid(svr.get_pid())
|
||||
except Exception as e:
|
||||
logger.error("Could not find PID for requested termsig. Full error: {}".format(e))
|
||||
else:
|
||||
logger.error("Could not find PID for requested termsig. Full error: svr.get_pid() = FALSE")
|
||||
try:
|
||||
svr.kill()
|
||||
except Exception as e:
|
||||
logger.error("Could not find PID for requested termsig. Full error: {}".format(e))
|
||||
return
|
||||
elif page == "eula":
|
||||
server_id = self.get_argument('id', None)
|
||||
svr = self.controller.get_server_obj(server_id)
|
||||
svr.agree_eula(user_data['user_id'])
|
||||
|
||||
elif page == "restore_backup":
|
||||
if not permissions['Backup'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Backups")
|
||||
return
|
||||
server_id = bleach.clean(self.get_argument('id', None))
|
||||
zip_name = bleach.clean(self.get_argument('zip_file', None))
|
||||
svr_obj = self.controller.servers.get_server_obj(server_id)
|
||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
||||
backup_path = svr_obj.backup_path
|
||||
if helper.validate_traversal(backup_path, zip_name):
|
||||
tempDir = helper.unzip_backup_archive(backup_path, zip_name)
|
||||
new_server = self.controller.import_zip_server(svr_obj.server_name, tempDir, server_data['executable'], '1', '2', server_data['server_port'])
|
||||
new_server_id = new_server
|
||||
new_server = self.controller.get_server_data(new_server)
|
||||
self.controller.rename_backup_dir(server_id, new_server_id, new_server['server_uuid'])
|
||||
self.controller.remove_server(server_id, True)
|
||||
self.redirect('/panel/dashboard')
|
||||
|
||||
elif page == "unzip_server":
|
||||
path = self.get_argument('path', None)
|
||||
helper.unzipServer(path, exec_user_id)
|
||||
return
|
||||
|
||||
|
||||
@tornado.web.authenticated
|
||||
def delete(self, page):
|
||||
user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
server_id = self.get_argument('id', None)
|
||||
exec_user_id = user_data['user_id']
|
||||
exec_user = helper_users.get_user(exec_user_id)
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
if page == "del_file":
|
||||
file_path = self.get_body_argument('file_path', default=None, strip=True)
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
console.warning("delete {} for server {}".format(file_path, server_id))
|
||||
|
||||
if not self.check_server_id(server_id, 'del_file'): return
|
||||
if not self.check_server_id(server_id, 'del_file'):
|
||||
return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not (helper.in_path(server_info['path'], file_path) \
|
||||
or helper.in_path(server_info['backup_path'], file_path)) \
|
||||
if not (helper.in_path(helper.get_os_understandable_path(server_info['path']), file_path) \
|
||||
or helper.in_path(helper.get_os_understandable_path(server_info['backup_path']), file_path)) \
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
@ -233,8 +341,45 @@ class AjaxHandler(BaseHandler):
|
||||
# Delete the file
|
||||
os.remove(file_path)
|
||||
|
||||
if page == "del_task":
|
||||
if not permissions['Schedule'] in user_perms:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Tasks")
|
||||
else:
|
||||
sch_id = self.get_argument('schedule_id', '-404')
|
||||
self.tasks_manager.remove_job(sch_id)
|
||||
|
||||
if page == "del_backup":
|
||||
if not permissions['Backup'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Backups")
|
||||
return
|
||||
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
console.warning("delete {} for server {}".format(file_path, server_id))
|
||||
|
||||
if not self.check_server_id(server_id, 'del_file'):
|
||||
return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not (helper.in_path(helper.get_os_understandable_path(server_info['path']), file_path) \
|
||||
or helper.in_path(helper.get_os_understandable_path(server_info['backup_path']), file_path)) \
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
return
|
||||
|
||||
# Delete the file
|
||||
if helper.validate_traversal(helper.get_os_understandable_path(server_info['path']), file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
elif page == "del_dir":
|
||||
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
dir_path = helper.get_os_understandable_path(self.get_body_argument('dir_path', default=None, strip=True))
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
console.warning("delete {} for server {}".format(dir_path, server_id))
|
||||
@ -243,7 +388,7 @@ class AjaxHandler(BaseHandler):
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not helper.in_path(server_info['path'], dir_path) \
|
||||
if not helper.in_path(helper.get_os_understandable_path(server_info['path']), dir_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning("Invalid path in del_file ajax call ({})".format(dir_path))
|
||||
console.warning("Invalid path in del_file ajax call ({})".format(dir_path))
|
||||
@ -251,15 +396,24 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
# Delete the directory
|
||||
# os.rmdir(dir_path) # Would only remove empty directories
|
||||
shutil.rmtree(dir_path) # Removes also when there are contents
|
||||
if helper.validate_traversal(helper.get_os_understandable_path(server_info['path']), dir_path):
|
||||
shutil.rmtree(dir_path) # Removes also when there are contents
|
||||
|
||||
elif page == "delete_server":
|
||||
if not permissions['Config'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
logger.info(
|
||||
"Removing server from panel for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
|
||||
self.controller.remove_server(server_id, False)
|
||||
|
||||
elif page == "delete_server_files":
|
||||
if not permissions['Config'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
logger.info(
|
||||
"Removing server and all associated files for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
|
||||
@ -267,15 +421,34 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
@tornado.web.authenticated
|
||||
def put(self, page):
|
||||
user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
server_id = self.get_argument('id', None)
|
||||
exec_user_id = user_data['user_id']
|
||||
exec_user = helper_users.get_user(exec_user_id)
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
if page == "save_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
|
||||
file_path = self.get_body_argument('file_path', default=None, strip=True)
|
||||
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'save_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in save_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in save_file ajax call ({})".format(file_path))
|
||||
@ -286,7 +459,11 @@ class AjaxHandler(BaseHandler):
|
||||
file_object.write(file_contents)
|
||||
|
||||
elif page == "rename_item":
|
||||
item_path = self.get_body_argument('item_path', default=None, strip=True)
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
item_path = helper.get_os_understandable_path(self.get_body_argument('item_path', default=None, strip=True))
|
||||
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
@ -298,7 +475,7 @@ class AjaxHandler(BaseHandler):
|
||||
console.warning("Invalid path(s) in rename_item ajax call")
|
||||
return
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], item_path) \
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), item_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(item_path)):
|
||||
logger.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
|
||||
console.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
|
||||
@ -306,7 +483,7 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], new_item_path) \
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), new_item_path) \
|
||||
or helper.check_path_exists(os.path.abspath(new_item_path)):
|
||||
logger.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
|
||||
console.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
|
||||
@ -314,6 +491,7 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
# RENAME
|
||||
os.rename(item_path, new_item_path)
|
||||
|
||||
def check_server_id(self, server_id, page_name):
|
||||
if server_id is None:
|
||||
logger.warning("Server ID not defined in {} ajax call ({})".format(page_name, server_id))
|
||||
|
@ -25,11 +25,9 @@ class HTTPHandlerPage(BaseHandler):
|
||||
def get(self, page):
|
||||
url = self.request.full_url
|
||||
port = 443
|
||||
print(url)
|
||||
if url[len(url)-1] == '/':
|
||||
url = url.strip(url[len(url)-1])
|
||||
url_list = url.split('/')
|
||||
print(url_list)
|
||||
if url_list[0] != "":
|
||||
primary_url = url_list[0] + ":"+str(port)+"/"
|
||||
backup_url = url_list[0] + ":" +str(helper.get_setting["https_port"]) +"/"
|
||||
|
@ -1,3 +1,4 @@
|
||||
from tempfile import tempdir
|
||||
from app.classes.shared.translation import Translation
|
||||
import json
|
||||
import logging
|
||||
@ -7,27 +8,52 @@ import bleach
|
||||
import time
|
||||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
from cron_validator import CronValidator
|
||||
|
||||
from tornado import locale
|
||||
from tornado import iostream
|
||||
|
||||
from tornado.ioloop import IOLoop
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import Users, installer
|
||||
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server, Permissions_Servers
|
||||
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty, Permissions_Crafty
|
||||
from app.classes.models.management import management_helper
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.websocket_helper import WebSocketHelper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PanelHandler(BaseHandler):
|
||||
|
||||
# Server fetching, spawned asynchronously
|
||||
# TODO: Make the related front-end elements update with AJAX
|
||||
def fetch_server_data(self, page_data):
|
||||
total_players = 0
|
||||
for server in page_data['servers']:
|
||||
total_players += len(self.controller.stats.get_server_players(server['server_data']['server_id']))
|
||||
page_data['num_players'] = total_players
|
||||
|
||||
for s in page_data['servers']:
|
||||
try:
|
||||
data = json.loads(s['int_ping_results'])
|
||||
s['int_ping_results'] = data
|
||||
except Exception as e:
|
||||
logger.error("Failed server data for page with error: {} ".format(e))
|
||||
|
||||
return page_data
|
||||
|
||||
|
||||
@tornado.web.authenticated
|
||||
def get(self, page):
|
||||
async def get(self, page):
|
||||
error = bleach.clean(self.get_argument('error', "WTF Error!"))
|
||||
|
||||
template = "panel/denied.html"
|
||||
@ -55,6 +81,7 @@ class PanelHandler(BaseHandler):
|
||||
page_data = {
|
||||
# todo: make this actually pull and compare version data
|
||||
'update_available': False,
|
||||
'serverTZ': time.tzname,
|
||||
'version_data': helper.get_version_string(),
|
||||
'user_data': exec_user_data,
|
||||
'user_role' : exec_user_role,
|
||||
@ -87,9 +114,11 @@ class PanelHandler(BaseHandler):
|
||||
elif page == 'credits':
|
||||
with open(helper.credits_cache) as republic_credits_will_do:
|
||||
credits = json.load(republic_credits_will_do)
|
||||
page_data["patreons"] = credits["patreons"]
|
||||
timestamp = credits["lastUpdate"] / 1000.0
|
||||
page_data["patrons"] = credits["patrons"]
|
||||
page_data["staff"] = credits["staff"]
|
||||
page_data["translations"] = credits["translations"]
|
||||
page_data["lastUpdate"] = str(datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S'))
|
||||
template = "panel/credits.html"
|
||||
|
||||
elif page == 'contribute':
|
||||
@ -119,14 +148,23 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
elif page == 'dashboard':
|
||||
if exec_user['superuser'] == 1:
|
||||
page_data['servers'] = self.controller.servers.get_all_servers_stats()
|
||||
try:
|
||||
page_data['servers'] = self.controller.servers.get_all_servers_stats()
|
||||
except IndexError:
|
||||
self.controller.stats.record_stats()
|
||||
page_data['servers'] = self.controller.servers.get_all_servers_stats()
|
||||
|
||||
for data in page_data['servers']:
|
||||
try:
|
||||
data['stats']['waiting_start'] = self.controller.servers.get_waiting_start(int(data['stats']['server_id']['server_id']))
|
||||
except:
|
||||
data['stats']['waiting_start'] = False
|
||||
else:
|
||||
user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id)
|
||||
try:
|
||||
user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id)
|
||||
except IndexError:
|
||||
self.controller.stats.record_stats()
|
||||
user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id)
|
||||
logger.debug("ASFR: {}".format(user_auth))
|
||||
page_data['servers'] = user_auth
|
||||
page_data['server_stats']['running'] = 0
|
||||
@ -141,17 +179,9 @@ class PanelHandler(BaseHandler):
|
||||
except:
|
||||
data['stats']['waiting_start'] = False
|
||||
|
||||
total_players = 0
|
||||
for server in page_data['servers']:
|
||||
total_players += len(self.controller.stats.get_server_players(server['server_data']['server_id']))
|
||||
page_data['num_players'] = total_players
|
||||
page_data['num_players'] = 0
|
||||
|
||||
for s in page_data['servers']:
|
||||
try:
|
||||
data = json.loads(s['int_ping_results'])
|
||||
s['int_ping_results'] = data
|
||||
except:
|
||||
pass
|
||||
IOLoop.current().add_callback(self.fetch_server_data, page_data)
|
||||
|
||||
template = "panel/dashboard.html"
|
||||
|
||||
@ -174,7 +204,7 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return False
|
||||
|
||||
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls']
|
||||
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls', 'tasks']
|
||||
|
||||
if subpage not in valid_subpages:
|
||||
logger.debug('not a valid subpage')
|
||||
@ -203,9 +233,52 @@ class PanelHandler(BaseHandler):
|
||||
}
|
||||
page_data['user_permissions'] = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
|
||||
if subpage == 'term':
|
||||
if not page_data['permissions']['Terminal'] in page_data['user_permissions']:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Terminal")
|
||||
return
|
||||
|
||||
if subpage == 'logs':
|
||||
if not page_data['permissions']['Logs'] in page_data['user_permissions']:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Logs")
|
||||
return
|
||||
|
||||
|
||||
if subpage == 'tasks':
|
||||
if not page_data['permissions']['Schedule'] in page_data['user_permissions']:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
|
||||
return
|
||||
page_data['schedules'] = management_helper.get_schedules_by_server(server_id)
|
||||
|
||||
if subpage == 'config':
|
||||
if not page_data['permissions']['Config'] in page_data['user_permissions']:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access Server Config")
|
||||
return
|
||||
|
||||
if subpage == 'files':
|
||||
if not page_data['permissions']['Files'] in page_data['user_permissions']:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access Files")
|
||||
return
|
||||
|
||||
|
||||
if subpage == "backup":
|
||||
if not page_data['permissions']['Backup'] in page_data['user_permissions']:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Backups")
|
||||
return
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
page_data['backup_config'] = self.controller.management.get_backup_config(server_id)
|
||||
page_data['backup_list'] = server.list_backups()
|
||||
self.controller.refresh_server_settings(server_id)
|
||||
try:
|
||||
page_data['backup_list'] = server.list_backups()
|
||||
except:
|
||||
page_data['backup_list'] = []
|
||||
page_data['backup_path'] = helper.wtol_path(server_info["backup_path"])
|
||||
|
||||
def get_banned_players_html():
|
||||
banned_players = self.controller.servers.get_banned_players(server_id)
|
||||
@ -227,6 +300,9 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
return html
|
||||
if subpage == "admin_controls":
|
||||
if not page_data['permissions']['Players'] in page_data['user_permissions']:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access")
|
||||
page_data['banned_players'] = get_banned_players_html()
|
||||
|
||||
# template = "panel/server_details.html"
|
||||
@ -252,8 +328,8 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
backup_file = os.path.abspath(os.path.join(server_info["backup_path"], file))
|
||||
if not helper.in_path(server_info["backup_path"], backup_file) \
|
||||
backup_file = os.path.abspath(os.path.join(helper.get_os_understandable_path(server_info["backup_path"]), file))
|
||||
if not helper.in_path(helper.get_os_understandable_path(server_info["backup_path"]), backup_file) \
|
||||
or not os.path.isfile(backup_file):
|
||||
self.redirect("/panel/error?error=Invalid path detected")
|
||||
return
|
||||
@ -315,7 +391,8 @@ class PanelHandler(BaseHandler):
|
||||
user_servers = self.controller.servers.get_authorized_servers(user.user_id)
|
||||
servers = []
|
||||
for server in user_servers:
|
||||
servers.append(server['server_name'])
|
||||
if server['server_name'] not in servers:
|
||||
servers.append(server['server_name'])
|
||||
new_item = {user.user_id: servers}
|
||||
auth_servers.update(new_item)
|
||||
data = {user.user_id: user_roles_list}
|
||||
@ -333,16 +410,22 @@ class PanelHandler(BaseHandler):
|
||||
page_data['role-servers'] = auth_role_servers
|
||||
page_data['user-roles'] = user_roles
|
||||
|
||||
if exec_user['superuser'] == 1:
|
||||
page_data['users'] = self.controller.users.get_all_users()
|
||||
page_data['roles'] = self.controller.roles.get_all_roles()
|
||||
else:
|
||||
page_data['users'] = self.controller.users.user_query(exec_user['user_id'])
|
||||
page_data['roles'] = self.controller.users.user_role_query(exec_user['user_id'])
|
||||
page_data['users'] = self.controller.users.user_query(exec_user['user_id'])
|
||||
page_data['roles'] = self.controller.users.user_role_query(exec_user['user_id'])
|
||||
|
||||
|
||||
for user in page_data['users']:
|
||||
if user.user_id != exec_user['user_id']:
|
||||
user.api_token = "********"
|
||||
if exec_user['superuser']:
|
||||
for user in self.controller.users.get_all_users():
|
||||
if user.superuser == 1:
|
||||
super_auth_servers = []
|
||||
super_auth_servers.append("Super User Access To All Servers")
|
||||
page_data['users'] = self.controller.users.get_all_users()
|
||||
page_data['roles'] = self.controller.roles.get_all_roles()
|
||||
page_data['auth-servers'][user.user_id] = super_auth_servers
|
||||
|
||||
template = "panel/panel_config.html"
|
||||
|
||||
elif page == "add_user":
|
||||
@ -350,6 +433,7 @@ class PanelHandler(BaseHandler):
|
||||
page_data['user'] = {}
|
||||
page_data['user']['username'] = ""
|
||||
page_data['user']['user_id'] = -1
|
||||
page_data['user']['email'] = ""
|
||||
page_data['user']['enabled'] = True
|
||||
page_data['user']['superuser'] = False
|
||||
page_data['user']['api_token'] = "N/A"
|
||||
@ -372,12 +456,108 @@ class PanelHandler(BaseHandler):
|
||||
page_data['quantity_server'] = self.controller.crafty_perms.list_all_crafty_permissions_quantity_limits()
|
||||
page_data['languages'] = []
|
||||
page_data['languages'].append(self.controller.users.get_user_lang_by_id(exec_user_id))
|
||||
if exec_user['superuser']:
|
||||
page_data['super-disabled'] = ''
|
||||
else:
|
||||
page_data['super-disabled'] = 'disabled'
|
||||
for file in os.listdir(os.path.join(helper.root_dir, 'app', 'translations')):
|
||||
if file.endswith('.json'):
|
||||
if file != str(page_data['languages'][0] + '.json'):
|
||||
page_data['languages'].append(file.split('.')[0])
|
||||
|
||||
template = "panel/panel_edit_user.html"
|
||||
|
||||
elif page == "add_schedule":
|
||||
server_id = self.get_argument('id', None)
|
||||
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
|
||||
page_data['active_link'] = 'tasks'
|
||||
page_data['permissions'] = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
page_data['user_permissions'] = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
exec_user_server_permissions = self.controller.server_perms.get_user_permissions_list(exec_user_id, server_id)
|
||||
page_data['server_data'] = self.controller.servers.get_server_data_by_id(server_id)
|
||||
page_data['server_stats'] = self.controller.servers.get_server_stats_by_id(server_id)
|
||||
page_data['new_schedule'] = True
|
||||
page_data['schedule'] = {}
|
||||
page_data['schedule']['server_id'] = server_id
|
||||
page_data['schedule']['schedule_id'] = ''
|
||||
page_data['schedule']['action'] = ""
|
||||
page_data['schedule']['enabled'] = True
|
||||
page_data['schedule']['command'] = ''
|
||||
page_data['schedule']['one_time'] = False
|
||||
page_data['schedule']['cron_string'] = ""
|
||||
page_data['schedule']['time'] = ""
|
||||
page_data['schedule']['interval'] = ""
|
||||
#we don't need to check difficulty here. We'll just default to basic for new schedules
|
||||
page_data['schedule']['difficulty'] = "basic"
|
||||
page_data['schedule']['interval_type'] = 'days'
|
||||
|
||||
if not Enum_Permissions_Server.Schedule in exec_user_server_permissions:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
|
||||
return
|
||||
|
||||
template = "panel/server_schedule_edit.html"
|
||||
|
||||
elif page == "edit_schedule":
|
||||
server_id = self.get_argument('id', None)
|
||||
sch_id = self.get_argument('sch_id', None)
|
||||
schedule = self.controller.management.get_scheduled_task_model(sch_id)
|
||||
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
|
||||
page_data['active_link'] = 'tasks'
|
||||
page_data['permissions'] = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
page_data['user_permissions'] = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
exec_user_server_permissions = self.controller.server_perms.get_user_permissions_list(exec_user_id, server_id)
|
||||
page_data['server_data'] = self.controller.servers.get_server_data_by_id(server_id)
|
||||
page_data['server_stats'] = self.controller.servers.get_server_stats_by_id(server_id)
|
||||
page_data['new_schedule'] = False
|
||||
page_data['schedule'] = {}
|
||||
page_data['schedule']['server_id'] = server_id
|
||||
page_data['schedule']['schedule_id'] = schedule.schedule_id
|
||||
page_data['schedule']['action'] = schedule.action
|
||||
#we check here to see if the command is any of the default ones. We do not want a user changing to a custom command and seeing our command there.
|
||||
if schedule.action != 'start' or schedule.action != 'stop' or schedule.action != 'restart' or schedule.action != 'backup':
|
||||
page_data['schedule']['command'] = schedule.command
|
||||
else:
|
||||
page_data['schedule']['command'] = ''
|
||||
page_data['schedule']['enabled'] = schedule.enabled
|
||||
page_data['schedule']['one_time'] = schedule.one_time
|
||||
page_data['schedule']['cron_string'] = schedule.cron_string
|
||||
page_data['schedule']['time'] = schedule.start_time
|
||||
page_data['schedule']['interval'] = schedule.interval
|
||||
page_data['schedule']['interval_type'] = schedule.interval_type
|
||||
if schedule.cron_string == '':
|
||||
difficulty = 'basic'
|
||||
else:
|
||||
difficulty = 'advanced'
|
||||
page_data['schedule']['difficulty'] = difficulty
|
||||
|
||||
if sch_id == None or server_id == None:
|
||||
self.redirect("/panel/error?error=Invalid server ID or Schedule ID")
|
||||
|
||||
if not Enum_Permissions_Server.Schedule in exec_user_server_permissions:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
|
||||
return
|
||||
|
||||
template = "panel/server_schedule_edit.html"
|
||||
|
||||
elif page == "edit_user":
|
||||
user_id = self.get_argument('id', None)
|
||||
@ -397,7 +577,13 @@ class PanelHandler(BaseHandler):
|
||||
page_data['quantity_server'] = self.controller.crafty_perms.list_crafty_permissions_quantity_limits(user_id)
|
||||
page_data['languages'] = []
|
||||
page_data['languages'].append(self.controller.users.get_user_lang_by_id(user_id))
|
||||
for file in os.listdir(os.path.join(helper.root_dir, 'app', 'translations')):
|
||||
#checks if super user. If not we disable the button.
|
||||
if exec_user['superuser'] and str(exec_user['user_id']) != str(user_id):
|
||||
page_data['super-disabled'] = ''
|
||||
else:
|
||||
page_data['super-disabled'] = 'disabled'
|
||||
|
||||
for file in sorted(os.listdir(os.path.join(helper.root_dir, 'app', 'translations'))):
|
||||
if file.endswith('.json'):
|
||||
if file != str(page_data['languages'][0] + '.json'):
|
||||
page_data['languages'].append(file.split('.')[0])
|
||||
@ -407,8 +593,6 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
elif Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
if str(user_id) != str(exec_user_id):
|
||||
print("USER ID ", user_id)
|
||||
print("EXEC ID ", exec_user_id)
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a user editor")
|
||||
return
|
||||
|
||||
@ -419,14 +603,21 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
if exec_user['user_id'] != page_data['user']['user_id']:
|
||||
page_data['user']['api_token'] = "********"
|
||||
|
||||
if exec_user['email'] == 'default@example.com':
|
||||
page_data['user']['email'] = ""
|
||||
template = "panel/panel_edit_user.html"
|
||||
|
||||
elif page == "remove_user":
|
||||
user_id = bleach.clean(self.get_argument('id', None))
|
||||
|
||||
if not exec_user['superuser']:
|
||||
|
||||
if not exec_user['superuser'] and Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
return
|
||||
|
||||
elif str(exec_user_id) == str(user_id):
|
||||
self.redirect("/panel/error?error=Unauthorized access: you cannot delete yourself")
|
||||
return
|
||||
elif user_id is None:
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
return
|
||||
@ -532,7 +723,7 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
elif page == 'download_file':
|
||||
server_id = self.get_argument('id', None)
|
||||
file = self.get_argument('path', "")
|
||||
file = helper.get_os_understandable_path(self.get_argument('path', ""))
|
||||
name = self.get_argument('name', "")
|
||||
|
||||
if server_id is None:
|
||||
@ -551,7 +742,7 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
|
||||
if not helper.in_path(server_info["path"], file) \
|
||||
if not helper.in_path(helper.get_os_understandable_path(server_info["path"]), file) \
|
||||
or not os.path.isfile(file):
|
||||
self.redirect("/panel/error?error=Invalid path detected")
|
||||
return
|
||||
@ -580,6 +771,46 @@ class PanelHandler(BaseHandler):
|
||||
del chunk
|
||||
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
|
||||
|
||||
elif page == 'download_support_package':
|
||||
tempZipStorage = exec_user['support_logs']
|
||||
#We'll reset the support path for this user now.
|
||||
self.controller.users.set_support_path(exec_user_id, "")
|
||||
|
||||
self.set_header('Content-Type', 'application/octet-stream')
|
||||
self.set_header('Content-Disposition', 'attachment; filename=' + "support_logs.zip")
|
||||
chunk_size = 1024 * 1024 * 4 # 4 MiB
|
||||
if tempZipStorage != '':
|
||||
with open(tempZipStorage, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
try:
|
||||
self.write(chunk) # write the chunk to response
|
||||
self.flush() # send the chunk to client
|
||||
except iostream.StreamClosedError:
|
||||
# this means the client has closed the connection
|
||||
# so break the loop
|
||||
break
|
||||
finally:
|
||||
# deleting the chunk is very important because
|
||||
# if many clients are downloading files at the
|
||||
# same time, the chunks in memory will keep
|
||||
# increasing and will eat up the RAM
|
||||
del chunk
|
||||
self.redirect('/panel/dashboard')
|
||||
else:
|
||||
self.redirect('/panel/error?error=No path found for support logs')
|
||||
return
|
||||
|
||||
elif page == "support_logs":
|
||||
logger.info("Support logs requested. Packinging logs for user with ID: {}".format(exec_user_id))
|
||||
logs_thread = threading.Thread(target=self.controller.package_support_logs, daemon=True, args=(exec_user,), name='{}_logs_thread'.format(exec_user['user_id']))
|
||||
logs_thread.start()
|
||||
self.redirect('/panel/dashboard')
|
||||
return
|
||||
|
||||
|
||||
|
||||
self.render(
|
||||
template,
|
||||
@ -594,7 +825,18 @@ class PanelHandler(BaseHandler):
|
||||
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
exec_user_id = exec_user_data['user_id']
|
||||
exec_user = self.controller.users.get_user_by_id(exec_user_id)
|
||||
|
||||
server_id = self.get_argument('id', None)
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
exec_user_role = set()
|
||||
if exec_user['superuser'] == 1:
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
@ -608,17 +850,26 @@ class PanelHandler(BaseHandler):
|
||||
exec_user_role.add(role['role_name'])
|
||||
|
||||
if page == 'server_detail':
|
||||
if not permissions['Config'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
server_name = self.get_argument('server_name', None)
|
||||
server_path = self.get_argument('server_path', None)
|
||||
log_path = self.get_argument('log_path', None)
|
||||
executable = self.get_argument('executable', None)
|
||||
execution_command = self.get_argument('execution_command', None)
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
if exec_user['superuser']:
|
||||
server_path = self.get_argument('server_path', None)
|
||||
log_path = self.get_argument('log_path', None)
|
||||
executable = self.get_argument('executable', None)
|
||||
execution_command = self.get_argument('execution_command', None)
|
||||
server_ip = self.get_argument('server_ip', None)
|
||||
server_port = self.get_argument('server_port', None)
|
||||
executable_update_url = self.get_argument('executable_update_url', None)
|
||||
else:
|
||||
execution_command = server_obj.execution_command
|
||||
executable = server_obj.executable
|
||||
stop_command = self.get_argument('stop_command', None)
|
||||
auto_start_delay = self.get_argument('auto_start_delay', '10')
|
||||
server_ip = self.get_argument('server_ip', None)
|
||||
server_port = self.get_argument('server_port', None)
|
||||
executable_update_url = self.get_argument('executable_update_url', None)
|
||||
auto_start = int(float(self.get_argument('auto_start', '0')))
|
||||
crash_detection = int(float(self.get_argument('crash_detection', '0')))
|
||||
logs_delete_after = int(float(self.get_argument('logs_delete_after', '0')))
|
||||
@ -637,22 +888,39 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
#TODO use controller method
|
||||
Servers.update({
|
||||
Servers.server_name: server_name,
|
||||
Servers.path: server_path,
|
||||
Servers.log_path: log_path,
|
||||
Servers.executable: executable,
|
||||
Servers.execution_command: execution_command,
|
||||
Servers.stop_command: stop_command,
|
||||
Servers.auto_start_delay: auto_start_delay,
|
||||
Servers.server_ip: server_ip,
|
||||
Servers.server_port: server_port,
|
||||
Servers.auto_start: auto_start,
|
||||
Servers.executable_update_url: executable_update_url,
|
||||
Servers.crash_detection: crash_detection,
|
||||
Servers.logs_delete_after: logs_delete_after,
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
server_settings = self.controller.get_server_data(server_id)
|
||||
stale_executable = server_obj.executable
|
||||
#Compares old jar name to page data being passed. If they are different we replace the executable name in the
|
||||
if str(stale_executable) != str(executable):
|
||||
execution_command = execution_command.replace(str(stale_executable), str(executable))
|
||||
|
||||
server_obj.server_name = server_name
|
||||
if exec_user['superuser']:
|
||||
if helper.validate_traversal(helper.get_servers_root_dir(), server_path):
|
||||
server_obj.path = server_path
|
||||
server_obj.log_path = log_path
|
||||
if helper.validate_traversal(helper.get_servers_root_dir(), executable):
|
||||
server_obj.executable = executable
|
||||
server_obj.execution_command = execution_command
|
||||
server_obj.server_ip = server_ip
|
||||
server_obj.server_port = server_port
|
||||
server_obj.executable_update_url = executable_update_url
|
||||
else:
|
||||
server_obj.path = server_obj.path
|
||||
server_obj.log_path = server_obj.log_path
|
||||
server_obj.executable = server_obj.executable
|
||||
print(server_obj.execution_command)
|
||||
server_obj.execution_command = server_obj.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
|
||||
server_obj.stop_command = stop_command
|
||||
server_obj.auto_start_delay = auto_start_delay
|
||||
server_obj.auto_start = auto_start
|
||||
server_obj.crash_detection = crash_detection
|
||||
server_obj.logs_delete_after = logs_delete_after
|
||||
self.controller.servers.update_server(server_obj)
|
||||
|
||||
self.controller.refresh_server_settings(server_id)
|
||||
|
||||
@ -666,15 +934,16 @@ class PanelHandler(BaseHandler):
|
||||
if page == "server_backup":
|
||||
logger.debug(self.request.arguments)
|
||||
server_id = self.get_argument('id', None)
|
||||
backup_path = bleach.clean(self.get_argument('backup_path', None))
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
if exec_user['superuser']:
|
||||
backup_path = bleach.clean(self.get_argument('backup_path', None))
|
||||
else:
|
||||
backup_path = server_obj.backup_path
|
||||
max_backups = bleach.clean(self.get_argument('max_backups', None))
|
||||
try:
|
||||
enabled = int(float(bleach.clean(self.get_argument('auto_enabled'), '0')))
|
||||
except Exception as e:
|
||||
enabled = '0'
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if not permissions['Backup'] in user_perms:
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: User not authorized")
|
||||
return
|
||||
elif server_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
@ -685,17 +954,10 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
if backup_path is not None:
|
||||
if enabled == '0':
|
||||
Servers.update({
|
||||
Servers.backup_path: backup_path
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
self.controller.management.set_backup_config(server_id, max_backups=max_backups, auto_enabled=False)
|
||||
else:
|
||||
Servers.update({
|
||||
Servers.backup_path: backup_path
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
self.controller.management.set_backup_config(server_id, max_backups=max_backups, auto_enabled=True)
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
server_obj.backup_path = backup_path
|
||||
self.controller.servers.update_server(server_obj)
|
||||
self.controller.management.set_backup_config(server_id, max_backups=max_backups)
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited server {}: updated backups".format(server_id),
|
||||
@ -704,14 +966,269 @@ class PanelHandler(BaseHandler):
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
|
||||
|
||||
|
||||
if page == "new_schedule":
|
||||
server_id = bleach.clean(self.get_argument('id', None))
|
||||
difficulty = bleach.clean(self.get_argument('difficulty', None))
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
enabled = bleach.clean(self.get_argument('enabled', '0'))
|
||||
if difficulty == 'basic':
|
||||
action = bleach.clean(self.get_argument('action', None))
|
||||
interval = bleach.clean(self.get_argument('interval', None))
|
||||
interval_type = bleach.clean(self.get_argument('interval_type', None))
|
||||
#only check for time if it's number of days
|
||||
if interval_type == "days":
|
||||
time = bleach.clean(self.get_argument('time', None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument('command', None))
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
else:
|
||||
interval_type = ''
|
||||
cron_string = bleach.clean(self.get_argument('cron', ''))
|
||||
try:
|
||||
CronValidator.parse(cron_string)
|
||||
except Exception as e:
|
||||
self.redirect("/panel/error?error=INVALID FORMAT: Invalid Cron Format. {}".format(e))
|
||||
return
|
||||
action = bleach.clean(self.get_argument('action', None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument('command', None))
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
if bleach.clean(self.get_argument('enabled', '0')) == '1':
|
||||
enabled = True
|
||||
else:
|
||||
enabled = False
|
||||
if bleach.clean(self.get_argument('one_time', '0')) == '1':
|
||||
one_time = True
|
||||
else:
|
||||
one_time = False
|
||||
|
||||
if not exec_user['superuser'] and not permissions['Backup'] in user_perms:
|
||||
self.redirect("/panel/error?error=Unauthorized access: User not authorized")
|
||||
return
|
||||
elif server_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
minute = datetime.datetime.now().minute
|
||||
hour = datetime.datetime.now().hour
|
||||
if minute < 10:
|
||||
minute = '0' + str(minute)
|
||||
if hour < 10:
|
||||
hour = '0'+str(hour)
|
||||
current_time = str(hour)+':'+str(minute)
|
||||
|
||||
if interval_type == "days":
|
||||
job_data = {
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"start_time": time,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"cron_string": ''
|
||||
}
|
||||
elif difficulty == "advanced":
|
||||
job_data = {
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": '',
|
||||
"interval": '',
|
||||
"command": '',
|
||||
#We'll base every interval off of a midnight start time.
|
||||
"start_time": '00:00',
|
||||
"command": command,
|
||||
"cron_string": cron_string,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time
|
||||
}
|
||||
else:
|
||||
job_data = {
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"enabled": enabled,
|
||||
#We'll base every interval off of a midnight start time.
|
||||
"start_time": '00:00',
|
||||
"one_time": one_time,
|
||||
"cron_string": ''
|
||||
}
|
||||
|
||||
self.tasks_manager.schedule_job(job_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited server {}: added scheduled job".format(server_id),
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
self.redirect("/panel/server_detail?id={}&subpage=tasks".format(server_id))
|
||||
|
||||
|
||||
if page == "edit_schedule":
|
||||
server_id = bleach.clean(self.get_argument('id', None))
|
||||
difficulty = bleach.clean(self.get_argument('difficulty', None))
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
enabled = bleach.clean(self.get_argument('enabled', '0'))
|
||||
if difficulty == 'basic':
|
||||
action = bleach.clean(self.get_argument('action', None))
|
||||
interval = bleach.clean(self.get_argument('interval', None))
|
||||
interval_type = bleach.clean(self.get_argument('interval_type', None))
|
||||
#only check for time if it's number of days
|
||||
if interval_type == "days":
|
||||
time = bleach.clean(self.get_argument('time', None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument('command', None))
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
else:
|
||||
interval_type = ''
|
||||
cron_string = bleach.clean(self.get_argument('cron', ''))
|
||||
sch_id = self.get_argument('sch_id', None)
|
||||
try:
|
||||
CronValidator.parse(cron_string)
|
||||
except Exception as e:
|
||||
self.redirect("/panel/error?error=INVALID FORMAT: Invalid Cron Format. {}".format(e))
|
||||
return
|
||||
action = bleach.clean(self.get_argument('action', None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument('command', None))
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
if bleach.clean(self.get_argument('enabled', '0'))=='1':
|
||||
enabled = True
|
||||
else:
|
||||
enabled = False
|
||||
if bleach.clean(self.get_argument('one_time', '0')) == '1':
|
||||
one_time = True
|
||||
else:
|
||||
one_time = False
|
||||
|
||||
if not exec_user['superuser'] and not permissions['Backup'] in user_perms:
|
||||
self.redirect("/panel/error?error=Unauthorized access: User not authorized")
|
||||
return
|
||||
elif server_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
minute = datetime.datetime.now().minute
|
||||
hour = datetime.datetime.now().hour
|
||||
if minute < 10:
|
||||
minute = '0' + str(minute)
|
||||
if hour < 10:
|
||||
hour = '0'+str(hour)
|
||||
current_time = str(hour)+':'+str(minute)
|
||||
|
||||
if interval_type == "days":
|
||||
job_data = {
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"start_time": time,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"cron_string": ''
|
||||
}
|
||||
elif difficulty == "advanced":
|
||||
job_data = {
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": '',
|
||||
"interval": '',
|
||||
"command": '',
|
||||
#We'll base every interval off of a midnight start time.
|
||||
"start_time": '00:00',
|
||||
"command": command,
|
||||
"cron_string": cron_string,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time
|
||||
}
|
||||
else:
|
||||
job_data = {
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"enabled": enabled,
|
||||
#We'll base every interval off of a midnight start time.
|
||||
"start_time": '00:00',
|
||||
"one_time": one_time,
|
||||
"cron_string": ''
|
||||
}
|
||||
sch_id = self.get_argument('sch_id', None)
|
||||
self.tasks_manager.update_job(sch_id, job_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited server {}: updated schedule".format(server_id),
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
self.redirect("/panel/server_detail?id={}&subpage=tasks".format(server_id))
|
||||
|
||||
|
||||
elif page == "edit_user":
|
||||
if bleach.clean(self.get_argument('username', None)) == 'system':
|
||||
self.redirect("/panel/error?error=Unauthorized access: system user is not editable")
|
||||
user_id = bleach.clean(self.get_argument('id', None))
|
||||
username = bleach.clean(self.get_argument('username', None))
|
||||
password0 = bleach.clean(self.get_argument('password0', None))
|
||||
password1 = bleach.clean(self.get_argument('password1', None))
|
||||
email = bleach.clean(self.get_argument('email', "default@example.com"))
|
||||
enabled = int(float(self.get_argument('enabled', '0')))
|
||||
regen_api = int(float(self.get_argument('regen_api', '0')))
|
||||
lang = bleach.clean(self.get_argument('language'), 'en_EN')
|
||||
if exec_user['superuser']:
|
||||
#Checks if user is trying to change super user status of self. We don't want that. Automatically make them stay super user since we know they are.
|
||||
if str(exec_user['user_id']) != str(user_id):
|
||||
superuser = bleach.clean(self.get_argument('superuser', '0'))
|
||||
else:
|
||||
superuser = '1'
|
||||
else:
|
||||
superuser = '0'
|
||||
if superuser == '1':
|
||||
superuser = True
|
||||
else:
|
||||
superuser = False
|
||||
|
||||
if Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
if str(user_id) != str(exec_user_id):
|
||||
@ -779,13 +1296,18 @@ class PanelHandler(BaseHandler):
|
||||
else:
|
||||
server_quantity[permission.name] = 0
|
||||
|
||||
# if email is None or "":
|
||||
# email = "default@example.com"
|
||||
|
||||
user_data = {
|
||||
"username": username,
|
||||
"password": password0,
|
||||
"email": email,
|
||||
"enabled": enabled,
|
||||
"regen_api": regen_api,
|
||||
"roles": roles,
|
||||
"lang": lang,
|
||||
"superuser": superuser,
|
||||
}
|
||||
user_crafty_data = {
|
||||
"permissions_mask": permissions_mask,
|
||||
@ -801,11 +1323,23 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
|
||||
elif page == "add_user":
|
||||
if bleach.clean(self.get_argument('username', None)).lower() == 'system':
|
||||
self.redirect("/panel/error?error=Unauthorized access: username system is reserved for the Crafty system. Please choose a different username.")
|
||||
return
|
||||
username = bleach.clean(self.get_argument('username', None))
|
||||
password0 = bleach.clean(self.get_argument('password0', None))
|
||||
password1 = bleach.clean(self.get_argument('password1', None))
|
||||
email = bleach.clean(self.get_argument('email', "default@example.com"))
|
||||
enabled = int(float(self.get_argument('enabled', '0'))),
|
||||
lang = bleach.clean(self.get_argument('lang', 'en_EN'))
|
||||
if exec_user['superuser']:
|
||||
superuser = bleach.clean(self.get_argument('superuser', '0'))
|
||||
else:
|
||||
superuser = '0'
|
||||
if superuser == '1':
|
||||
superuser = True
|
||||
else:
|
||||
superuser = False
|
||||
|
||||
if Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a user editor")
|
||||
@ -854,7 +1388,7 @@ class PanelHandler(BaseHandler):
|
||||
else:
|
||||
server_quantity[permission.name] = 0
|
||||
|
||||
user_id = self.controller.users.add_user(username, password=password0, enabled=enabled)
|
||||
user_id = self.controller.users.add_user(username, password=password0, email=email, enabled=enabled, superuser=superuser)
|
||||
user_data = {
|
||||
"roles": roles,
|
||||
'lang': lang
|
||||
@ -977,7 +1511,10 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
else:
|
||||
self.set_status(404)
|
||||
page_data = {}
|
||||
page_data['lang'] = locale.get("en_EN")
|
||||
self.render(
|
||||
"public/404.html",
|
||||
translate=self.translator.translate,
|
||||
data=page_data
|
||||
)
|
||||
|
@ -1,10 +1,13 @@
|
||||
from re import X
|
||||
import sys
|
||||
import json
|
||||
import libgravatar
|
||||
import logging
|
||||
import requests
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.helpers import Helpers, helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import fn
|
||||
@ -40,6 +43,7 @@ class PublicHandler(BaseHandler):
|
||||
def get(self, page=None):
|
||||
|
||||
error = bleach.clean(self.get_argument('error', "Invalid Login!"))
|
||||
error_msg = bleach.clean(self.get_argument('error_msg', ''))
|
||||
|
||||
page_data = {
|
||||
'version': helper.get_version_string(),
|
||||
@ -75,6 +79,7 @@ class PublicHandler(BaseHandler):
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
error_msg = error_msg
|
||||
)
|
||||
|
||||
def post(self, page=None):
|
||||
@ -89,18 +94,18 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
# if we don't have a user
|
||||
if not user_data:
|
||||
next_page = "/public/error?error=Login Failed"
|
||||
error_msg = "Inncorrect username or password. Please try again."
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
self.redirect(next_page)
|
||||
self.redirect('/public/login?error_msg={}'.format(error_msg))
|
||||
return
|
||||
|
||||
# if they are disabled
|
||||
if not user_data.enabled:
|
||||
next_page = "/public/error?error=Login Failed"
|
||||
error_msg = "User account disabled. Please contact your system administrator for more info."
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
self.redirect(next_page)
|
||||
self.redirect('/public/login?error_msg={}'.format(error_msg))
|
||||
return
|
||||
|
||||
login_result = helper.verify_pass(entered_password, user_data.password)
|
||||
@ -119,9 +124,27 @@ class PublicHandler(BaseHandler):
|
||||
# log this login
|
||||
self.controller.management.add_to_audit_log(user_data.user_id, "Logged in", 0, self.get_remote_ip())
|
||||
|
||||
if helper.get_setting("allow_nsfw_profile_pictures"):
|
||||
rating = "x"
|
||||
else:
|
||||
rating = "g"
|
||||
|
||||
|
||||
#Get grvatar hash for profile pictures
|
||||
if user_data.email != 'default@example.com' or "":
|
||||
g = libgravatar.Gravatar(libgravatar.sanitize_email(user_data.email))
|
||||
url = g.get_image(size=80, default="404", force_default=False, rating=rating, filetype_extension=False, use_ssl=True) # + "?d=404"
|
||||
if requests.head(url).status_code != 404:
|
||||
profile_url = url
|
||||
else:
|
||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
||||
else:
|
||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
||||
cookie_data = {
|
||||
"username": user_data.username,
|
||||
"user_id": user_data.user_id,
|
||||
"email": user_data.email,
|
||||
"profile_url": profile_url,
|
||||
"account_type": user_data.superuser,
|
||||
}
|
||||
|
||||
@ -132,9 +155,10 @@ class PublicHandler(BaseHandler):
|
||||
else:
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
error_msg = "Inncorrect username or password. Please try again."
|
||||
# log this failed login attempt
|
||||
self.controller.management.add_to_audit_log(user_data.user_id, "Tried to log in", 0, self.get_remote_ip())
|
||||
self.redirect('/public/error?error=Login Failed')
|
||||
self.redirect('/public/login?error_msg={}'.format(error_msg))
|
||||
else:
|
||||
self.redirect("/public/login")
|
||||
|
||||
|
@ -38,12 +38,17 @@ class ServerHandler(BaseHandler):
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
exec_user_role.add("Super User")
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
|
||||
list_roles = []
|
||||
for role in self.controller.roles.get_all_roles():
|
||||
list_roles.append(self.controller.roles.get_role(role.role_id))
|
||||
else:
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user_id)
|
||||
defined_servers = self.controller.servers.get_authorized_servers(exec_user_id)
|
||||
list_roles = []
|
||||
for r in exec_user['roles']:
|
||||
role = self.controller.roles.get_role(r)
|
||||
exec_user_role.add(role['role_name'])
|
||||
list_roles.append(self.controller.roles.get_role(role['role_id']))
|
||||
|
||||
template = "public/404.html"
|
||||
|
||||
@ -51,6 +56,7 @@ class ServerHandler(BaseHandler):
|
||||
'version_data': helper.get_version_string(),
|
||||
'user_data': exec_user_data,
|
||||
'user_role' : exec_user_role,
|
||||
'roles' : list_roles,
|
||||
'user_crafty_permissions' : exec_user_crafty_permissions,
|
||||
'crafty_permissions': {
|
||||
'Server_Creation': Enum_Permissions_Crafty.Server_Creation,
|
||||
@ -67,14 +73,16 @@ class ServerHandler(BaseHandler):
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True),
|
||||
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
|
||||
}
|
||||
if exec_user['superuser'] == 1:
|
||||
page_data['roles'] = list_roles
|
||||
|
||||
if page == "step1":
|
||||
if not exec_user['superuser'] and not self.controller.crafty_perms.can_create_server(exec_user_id):
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
|
||||
return
|
||||
|
||||
page_data['server_types'] = server_jar_obj.get_serverjar_data_sorted()
|
||||
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data_sorted())
|
||||
page_data['server_types'] = server_jar_obj.get_serverjar_data()
|
||||
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data())
|
||||
template = "server/wizard.html"
|
||||
|
||||
self.render(
|
||||
@ -131,7 +139,7 @@ class ServerHandler(BaseHandler):
|
||||
stop_command = server_data.get('stop_command')
|
||||
new_server_command = str(server_data.get('execution_command')).replace(server_uuid, new_server_uuid)
|
||||
new_executable = server_data.get('executable')
|
||||
new_server_log_file = str(server_data.get('log_path')).replace(server_uuid, new_server_uuid)
|
||||
new_server_log_file = str(helper.get_os_understandable_path(server_data.get('log_path'))).replace(server_uuid, new_server_uuid)
|
||||
auto_start = server_data.get('auto_start')
|
||||
auto_start_delay = server_data.get('auto_start_delay')
|
||||
crash_detection = server_data.get('crash_detection')
|
||||
@ -147,6 +155,10 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
if page == "step1":
|
||||
|
||||
if not exec_user['superuser']:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
else:
|
||||
user_roles = self.controller.roles.get_all_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', ''))
|
||||
@ -156,6 +168,10 @@ class ServerHandler(BaseHandler):
|
||||
import_server_path = bleach.clean(self.get_argument('server_path', ''))
|
||||
import_server_jar = bleach.clean(self.get_argument('server_jar', ''))
|
||||
server_parts = server.split("|")
|
||||
captured_roles = []
|
||||
for role in user_roles:
|
||||
if bleach.clean(self.get_argument(str(role), '')) == "on":
|
||||
captured_roles.append(role)
|
||||
|
||||
if not server_name:
|
||||
self.redirect("/panel/error?error=Server name cannot be empty!")
|
||||
@ -175,12 +191,13 @@ class ServerHandler(BaseHandler):
|
||||
self.get_remote_ip())
|
||||
elif import_type == 'import_zip':
|
||||
# here import_server_path means the zip path
|
||||
good_path = self.controller.verify_zip_server(import_server_path)
|
||||
zip_path = bleach.clean(self.get_argument('root_path'))
|
||||
good_path = helper.check_path_exists(zip_path)
|
||||
if not good_path:
|
||||
self.redirect("/panel/error?error=Zip file not found!")
|
||||
self.redirect("/panel/error?error=Temp path not found!")
|
||||
return
|
||||
|
||||
new_server_id = self.controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
|
||||
new_server_id = self.controller.import_zip_server(server_name, zip_path, import_server_jar, min_mem, max_mem, port)
|
||||
if new_server_id == "false":
|
||||
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
|
||||
return
|
||||
@ -188,6 +205,8 @@ class ServerHandler(BaseHandler):
|
||||
"imported a zip server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
#deletes temp dir
|
||||
shutil.rmtree(zip_path)
|
||||
else:
|
||||
if len(server_parts) != 2:
|
||||
self.redirect("/panel/error?error=Invalid server data")
|
||||
@ -202,12 +221,18 @@ class ServerHandler(BaseHandler):
|
||||
self.get_remote_ip())
|
||||
|
||||
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
|
||||
if not exec_user['superuser']:
|
||||
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
|
||||
role_id = self.controller.roles.add_role("Creator of Server with uuid={}".format(new_server_uuid))
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
self.controller.users.add_role_to_user(exec_user_id, role_id)
|
||||
self.controller.crafty_perms.add_server_creation(exec_user_id)
|
||||
if len(captured_roles) == 0:
|
||||
if not exec_user['superuser']:
|
||||
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
|
||||
role_id = self.controller.roles.add_role("Creator of Server with uuid={}".format(new_server_uuid))
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
self.controller.users.add_role_to_user(exec_user_id, role_id)
|
||||
self.controller.crafty_perms.add_server_creation(exec_user_id)
|
||||
|
||||
else:
|
||||
for role in captured_roles:
|
||||
role_id = role
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
|
||||
self.controller.stats.record_stats()
|
||||
self.redirect("/panel/dashboard")
|
||||
|
@ -175,9 +175,7 @@ class Webserver:
|
||||
self.HTTPS_Server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects)
|
||||
self.HTTPS_Server.listen(https_port)
|
||||
|
||||
logger.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
|
||||
logger.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
|
||||
console.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
|
||||
console.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
|
||||
|
||||
console.info("Server Init Complete: Listening For Connections:")
|
||||
|
@ -52,8 +52,8 @@ class UploadHandler(tornado.web.RequestHandler):
|
||||
filename = self.request.headers.get('X-FileName', None)
|
||||
full_path = os.path.join(path, filename)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], full_path):
|
||||
print(user_id, server_id, self.controller.servers.get_server_data_by_id(server_id)['path'], full_path)
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path):
|
||||
print(user_id, server_id, helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path)
|
||||
logger.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
|
||||
console.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
|
||||
self.do_upload = False
|
||||
|
@ -1,11 +1,13 @@
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from urllib.parse import parse_qsl
|
||||
from app.classes.models.users import Users
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -31,6 +33,14 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
self.request.remote_ip
|
||||
return remote_ip
|
||||
|
||||
def get_user_id(self):
|
||||
user_data_cookie_raw = self.get_secure_cookie('user_data')
|
||||
|
||||
if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'):
|
||||
user_data_cookie = user_data_cookie_raw.decode('utf-8')
|
||||
user_id = json.loads(user_data_cookie)['user_id']
|
||||
return user_id
|
||||
|
||||
def check_auth(self):
|
||||
user_data_cookie_raw = self.get_secure_cookie('user_data')
|
||||
|
||||
|
@ -42,15 +42,36 @@ class WebSocketHelper:
|
||||
def filter_fn(client):
|
||||
return client.page == page
|
||||
|
||||
clients = list(filter(filter_fn, self.clients))
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
def broadcast_user(self, user_id: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
return client.get_user_id() == user_id
|
||||
|
||||
for client in clients:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except Exception as e:
|
||||
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
def broadcast_user_page(self, page: str, user_id: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if client.get_user_id() != user_id:
|
||||
return False
|
||||
if client.page != page:
|
||||
return False
|
||||
return True
|
||||
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
def broadcast_user_page_params(self, page: str, params: dict, user_id: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if client.get_user_id() != user_id:
|
||||
return False
|
||||
if client.page != page:
|
||||
return False
|
||||
for key, param in params.items():
|
||||
if param != client.page_query_params.get(key, None):
|
||||
return False
|
||||
return True
|
||||
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
def broadcast_page_params(self, page: str, params: dict, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
@ -61,6 +82,9 @@ class WebSocketHelper:
|
||||
return False
|
||||
return True
|
||||
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
def broadcast_with_fn(self, filter_fn, event_type: str, data):
|
||||
clients = list(filter(filter_fn, self.clients))
|
||||
|
||||
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
|
@ -12,5 +12,6 @@
|
||||
"show_contribute_link": true,
|
||||
"virtual_terminal_lines": 70,
|
||||
"max_log_lines": 700,
|
||||
"keywords": ["help", "chunk"]
|
||||
"keywords": ["help", "chunk"],
|
||||
"allow_nsfw_profile_pictures": false
|
||||
}
|
||||
|
@ -1,150 +1,311 @@
|
||||
{
|
||||
"patreons": [
|
||||
{
|
||||
"name": "Dean of Approval",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Lightkeeper",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Lino",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas T",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Richard B",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "ryuk228",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Zedifus",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "John C",
|
||||
"level": "advocate"
|
||||
}
|
||||
],
|
||||
"staff": {
|
||||
"development": [
|
||||
{
|
||||
"name": "Phil Tarrant",
|
||||
"title": "Benevolent Dictator for Life",
|
||||
"loc": "Atlanta, GA",
|
||||
"tags": [ "Staff", "Developer", [ "BDFL", "https://en.wikipedia.org/wiki/Benevolent_dictator_for_life" ] ],
|
||||
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
|
||||
"pic": "/static/assets/images/credits/ptarrant_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "Pita Bread",
|
||||
"title": null,
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [ "Staff", null, "Community Leader" ],
|
||||
"blurb": "His interests include bread, Linux, and networking. He enjoys pumpkins, organizing, and long-winded emails, but hates wifi.",
|
||||
"pic": "/static/assets/images/credits/pita_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "macgeek",
|
||||
"title": null,
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [ "Staff", "Developer", "Project Manager" ],
|
||||
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
|
||||
"pic": "/static/assets/images/credits/macgeek_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "parzivaldewey",
|
||||
"title": null,
|
||||
"loc": "East Coast, USA",
|
||||
"tags": [ "Staff", "Developer", "Support Manager" ],
|
||||
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
|
||||
"pic": "/static/assets/images/credits/andrew_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "MC Gaming",
|
||||
"title": null,
|
||||
"loc": "Central, UK",
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "His interests include learning, Linux, programming. He loves pentesting apps and gaming.",
|
||||
"pic": "/static/assets/images/credits/mcgaming.png"
|
||||
},
|
||||
{
|
||||
"name": "Silversthorn",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "Often in it's cave, he sometimes goes out to help or do silly jokes. He's an IT clown (not the film), but seriously do the job when it's needed.",
|
||||
"pic": "/static/assets/images/credits/silversthorn.png"
|
||||
},
|
||||
{
|
||||
"name": "ThatOneLukas",
|
||||
"title": null,
|
||||
"loc": "Helsinki, FI",
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "Lukas enjoys bashing his head at the table while his code does not work",
|
||||
"pic": "/static/assets/images/credits/lukas_cropped.png"
|
||||
}
|
||||
],
|
||||
"support": [
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [ "Staff", "Wiki", null ],
|
||||
"blurb": "Silver enjoys helping others with their computer needs, writing documentation and loving her cat.",
|
||||
"pic": "/static/assets/images/credits/isilverfyre.png"
|
||||
},
|
||||
{
|
||||
"name": "Quentin",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
|
||||
"pic": "/static/assets/images/credits/qub3d.png"
|
||||
}
|
||||
],
|
||||
"retired": [
|
||||
{
|
||||
"name": "Kev Dagoat",
|
||||
"title": "Head of Development",
|
||||
"loc": "East Coast, AU",
|
||||
"tags": [ "Staff", "Developer", "HOD" ],
|
||||
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and Geeking over video cards.",
|
||||
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
|
||||
},
|
||||
{
|
||||
"name": "Manu",
|
||||
"title": null,
|
||||
"loc": "Eastern, CA",
|
||||
"tags": [ "Staff", "Developer", "Project Manager" ],
|
||||
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
|
||||
"pic": "/static/assets/images/credits/manu_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "UltraBlack",
|
||||
"title": null,
|
||||
"loc": "Bavaria, DE",
|
||||
"tags": [ "Staff", null, "Idea Manager" ],
|
||||
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
|
||||
"pic": "/static/assets/images/credits/ultrablack_cropped.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"translations": {
|
||||
"Lukas": [ "Finnish" ],
|
||||
"Manu": [ "French" ],
|
||||
"ptarrant": [ "Sarcasm", "Wit" ],
|
||||
"UltraBlack": [ "German" ]
|
||||
}
|
||||
}
|
||||
"staff": {
|
||||
"development": [
|
||||
{
|
||||
"name": "Phil Tarrant",
|
||||
"title": "Creator",
|
||||
"loc": "Southeast, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Ptarrant1"
|
||||
],
|
||||
"Creator"
|
||||
],
|
||||
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
|
||||
"pic": "/static/assets/images/credits/ptarrant.png"
|
||||
},
|
||||
{
|
||||
"name": "Pita Bread",
|
||||
"title": "Leadership Team",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/craftbreadth"
|
||||
],
|
||||
"Community Leader"
|
||||
],
|
||||
"blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.",
|
||||
"pic": "/static/assets/images/credits/pita.png"
|
||||
},
|
||||
{
|
||||
"name": "macgeek",
|
||||
"title": "Leadership Team",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/computergeek125"
|
||||
],
|
||||
"Project Manager"
|
||||
],
|
||||
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
|
||||
"pic": "/static/assets/images/credits/macgeek.png"
|
||||
},
|
||||
{
|
||||
"name": "parzivaldewey",
|
||||
"title": null,
|
||||
"loc": "East Coast, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/amcmanu3"
|
||||
],
|
||||
"Support Manager"
|
||||
],
|
||||
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
|
||||
"pic": "/static/assets/images/credits/parzivaldewey.png"
|
||||
},
|
||||
{
|
||||
"name": "Xithical",
|
||||
"title": null,
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/xithical"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.",
|
||||
"pic": "/static/assets/images/credits/xithical.png"
|
||||
},
|
||||
{
|
||||
"name": "MC Gaming",
|
||||
"title": null,
|
||||
"loc": "Central, UK",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/MCgamin1738"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.",
|
||||
"pic": "/static/assets/images/credits/mcgaming.png"
|
||||
},
|
||||
{
|
||||
"name": "Silversthorn",
|
||||
"title": null,
|
||||
"loc": "Provence, FR",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Silversthorn"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Developer at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"",
|
||||
"pic": "/static/assets/images/credits/silversthorn.png"
|
||||
},
|
||||
{
|
||||
"name": "ThatOneLukas",
|
||||
"title": null,
|
||||
"loc": "Helsinki, FI",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/LukasDoesDev"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Lukas enjoys bashing his head at the table while his code does not work",
|
||||
"pic": "/static/assets/images/credits/lukas.png"
|
||||
},
|
||||
{
|
||||
"name": "Zedifus",
|
||||
"title": null,
|
||||
"loc": "Scotland, UK",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Zedifus"
|
||||
],
|
||||
"DevOps"
|
||||
],
|
||||
"blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.",
|
||||
"pic": "/static/assets/images/credits/zedifus.jpg"
|
||||
}
|
||||
],
|
||||
"support": [
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Wiki",
|
||||
null
|
||||
],
|
||||
"blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.",
|
||||
"pic": "/static/assets/images/credits/isilverfyre.png"
|
||||
},
|
||||
{
|
||||
"name": "Quentin",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/qub3d"
|
||||
],
|
||||
"Wiki"
|
||||
],
|
||||
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
|
||||
"pic": "/static/assets/images/credits/qub3d.png"
|
||||
}
|
||||
],
|
||||
"retired": [
|
||||
{
|
||||
"name": "Kev Dagoat",
|
||||
"title": "Head of Development",
|
||||
"loc": "East Coast, AU",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/kevdagoat"
|
||||
],
|
||||
"HOD"
|
||||
],
|
||||
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.",
|
||||
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
|
||||
},
|
||||
{
|
||||
"name": "Manu",
|
||||
"title": null,
|
||||
"loc": "Eastern, CA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Developer",
|
||||
"Project Manager"
|
||||
],
|
||||
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
|
||||
"pic": "/static/assets/images/credits/manu.png"
|
||||
},
|
||||
{
|
||||
"name": "UltraBlack",
|
||||
"title": null,
|
||||
"loc": "Bavaria, DE",
|
||||
"tags": [
|
||||
"Staff",
|
||||
null,
|
||||
"Idea Manager"
|
||||
],
|
||||
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
|
||||
"pic": "/static/assets/images/credits/ultrablack.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"translations": {
|
||||
"ptarrant": [
|
||||
"Sarcasm",
|
||||
"Wit"
|
||||
],
|
||||
"Lukas": [
|
||||
"Finnish"
|
||||
],
|
||||
"Manu": [
|
||||
"French"
|
||||
],
|
||||
"Silversthorn": [
|
||||
"French"
|
||||
],
|
||||
"UltraBlack": [
|
||||
"German"
|
||||
],
|
||||
"Zedifus": [
|
||||
"LOLCAT (Kingdum ov Katz)"
|
||||
]
|
||||
},
|
||||
"patrons": [
|
||||
{
|
||||
"name": "Richard B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "JOHN C",
|
||||
"level": "Crafty Advocate"
|
||||
},
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Dean R",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "scott m",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Lino",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "PeterPorker3",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "Ewari",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "Thyodas",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "AngryPanda",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Chris T",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Adam B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Eric G",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Chris B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Emmet d",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Steven T",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Row H",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "vorboid",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "Justman",
|
||||
"level": "Crafty Sustainer"
|
||||
}
|
||||
],
|
||||
"lastUpdate": 1637635758855
|
||||
}
|
||||
|
@ -64,7 +64,7 @@
|
||||
"handlers": ["tornado_access_file_handler"],
|
||||
"propagate": false
|
||||
},
|
||||
"schedule": {
|
||||
"apscheduler": {
|
||||
"level": "INFO",
|
||||
"handlers": ["schedule_file_handler"],
|
||||
"propagate": false
|
||||
|
24
app/config/motd_format.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"dark_red":"§4",
|
||||
"red":"§c",
|
||||
"gold":"§6",
|
||||
"yellow":"§e",
|
||||
"dark_green":"§2",
|
||||
"green":"§a",
|
||||
"aqua":"§b",
|
||||
"dark_aqua":"§3",
|
||||
"dark_blue":"§1",
|
||||
"blue":"§9",
|
||||
"light_purple":"§d",
|
||||
"dark_purple":"§5",
|
||||
"white":"§f",
|
||||
"gray":"§7",
|
||||
"dark_gray":"§8",
|
||||
"black":"§0",
|
||||
"reset":"§r",
|
||||
"bold":"§l",
|
||||
"italic":"§o",
|
||||
"underlined":"§n",
|
||||
"strikethrough":"§m",
|
||||
"obfuscated":"§k"
|
||||
}
|
@ -2,5 +2,5 @@
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": 0,
|
||||
"meta": "alpha.2"
|
||||
"meta": "alpha.3"
|
||||
}
|
||||
|
@ -10449,10 +10449,10 @@ pre {
|
||||
padding-right: 80px; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]):before {
|
||||
border-color: #0b0b0b; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) .menu-title {
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) {
|
||||
color: #0b0b0b; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) .menu-arrow::before {
|
||||
color: #0b0b0b; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) .menu-arrow:before {
|
||||
color: #b9c0d3; }
|
||||
|
||||
/* style for off-canvas menu*/
|
||||
@media screen and (max-width: 991px) {
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 413 KiB |
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 243 KiB |
Before Width: | Height: | Size: 558 KiB After Width: | Height: | Size: 558 KiB |
Before Width: | Height: | Size: 842 KiB After Width: | Height: | Size: 842 KiB |
BIN
app/frontend/static/assets/images/credits/ptarrant.png
Normal file
After Width: | Height: | Size: 311 KiB |
Before Width: | Height: | Size: 5.6 MiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 856 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
BIN
app/frontend/static/assets/images/credits/xithical.png
Normal file
After Width: | Height: | Size: 420 KiB |
BIN
app/frontend/static/assets/images/credits/zedifus.jpg
Normal file
After Width: | Height: | Size: 246 KiB |
@ -30,6 +30,7 @@
|
||||
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
|
||||
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
|
||||
<link rel="stylesheet" href="/static/assets/css/crafty.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body class="dark-theme">
|
||||
@ -237,6 +238,81 @@ if (webSocket) {
|
||||
})
|
||||
});
|
||||
}
|
||||
if (webSocket) {
|
||||
webSocket.on('send_logs_bootbox', function (server_id) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if(x){
|
||||
x.remove()}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if(x){
|
||||
x.remove()}
|
||||
bootbox.alert({
|
||||
title: 'Download Support Logs?',
|
||||
message: "We've finished preparing your support logs. Please click download to download",
|
||||
buttons: {
|
||||
ok: {
|
||||
label: 'Download',
|
||||
className: 'btn-info'
|
||||
}
|
||||
},
|
||||
callback: function(){
|
||||
console.log("in callback")
|
||||
location.href="/panel/download_support_package";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_eula_bootbox', function (server_id) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if(x){
|
||||
x.remove()}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if(x){
|
||||
x.remove()}
|
||||
bootbox.confirm({
|
||||
title: '{% raw translate("error", "eulaTitle", data['lang']) %}',
|
||||
message: '{% raw translate("error", "eulaMsg", data['lang']) %} <br><br><a href="https://account.mojang.com/documents/minecraft_eula" target="_blank">EULA</a><br><br>{% raw translate("error", "eulaAgree", data['lang']) %}',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes',
|
||||
className: 'btn-info'
|
||||
},
|
||||
cancel: {
|
||||
label: 'No',
|
||||
className: 'btn-secondary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if(result == true){
|
||||
eulaAgree(server_id.id)
|
||||
}
|
||||
else {
|
||||
location.reload()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function eulaAgree (server_id, command){
|
||||
<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/eula?id='+ server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
location.reload();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function warn(message) {
|
||||
|
@ -2,8 +2,9 @@
|
||||
<footer class="footer">
|
||||
<div class="container-fluid ">
|
||||
|
||||
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
|
||||
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}
|
||||
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 - 2022 <a href="https://craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
|
||||
|
||||
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -18,16 +18,18 @@
|
||||
|
||||
<li class="nav-item dropdown user-dropdown">
|
||||
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
|
||||
<img class="img-xs rounded-circle" src="/static/assets/images/faces-clipart/pic-1.png" alt="Profile image"> </a>
|
||||
<img class="img-xs rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image"> </a>
|
||||
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
|
||||
<div class="dropdown-header text-center">
|
||||
<img class="img-md rounded-circle" src="/static/assets/images/faces-clipart/pic-1.png" alt="Profile image">
|
||||
<img class="img-md rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image">
|
||||
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
|
||||
<p class="font-weight-light text-muted mb-0">Roles: </p>
|
||||
{% for r in data['user_role'] %}
|
||||
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
|
||||
{% end %}
|
||||
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
|
||||
</div>
|
||||
<a class="dropdown-item" href="/panel/support_logs"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i> Support Logs</i></a>
|
||||
{% if "Super User" in data['user_role'] %}
|
||||
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i> Activity</a>
|
||||
{% end %}
|
||||
|
@ -21,40 +21,51 @@
|
||||
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 grid-margin">
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-history"></i> Audit Logs</h4>
|
||||
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table" id="audit_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td>Time</td>
|
||||
<td>Action</td>
|
||||
<td>Server ID</td>
|
||||
<td>IP</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data['audit_logs'] %}
|
||||
<tr>
|
||||
<td>{{ row['user_name'] }}</td>
|
||||
<td>
|
||||
{{ row['created'].strftime('%m-%d-%Y %H:%M:%S') }}
|
||||
</td>
|
||||
<td>{{ row['log_msg'] }}</td>
|
||||
<td>{{ row['server_id'] }}</td>
|
||||
<td>{{ row['source_ip'] }}</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<td>Username</td>
|
||||
<td>Time</td>
|
||||
<td>Action</td>
|
||||
<td>Server ID</td>
|
||||
<td>IP</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data['audit_logs'] %}
|
||||
<tr>
|
||||
<td>{{ row['user_name'] }}</td>
|
||||
<td>
|
||||
{{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }}
|
||||
</td>
|
||||
<td>{{ row['log_msg'] }}</td>
|
||||
<td>{{ row['server_id'] }}</td>
|
||||
<td>{{ row['source_ip'] }}</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.popover-body{
|
||||
color: white !important;;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@ -69,8 +80,35 @@
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
$('#audit_table').DataTable();
|
||||
$('#audit_table').DataTable({
|
||||
'order': [1, 'desc']
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
|
||||
});
|
||||
$(window).ready(function(){
|
||||
$('body').click(function(){
|
||||
$('.too_small').popover("hide");
|
||||
});
|
||||
});
|
||||
$(window).resize(function() {
|
||||
// This will execute whenever the window is resized
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
else{
|
||||
$('.too_small').popover("hide");
|
||||
} // New width
|
||||
});
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -23,7 +23,7 @@
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-3 grid-margin">
|
||||
<div class="col-md-4 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
@ -31,8 +31,8 @@
|
||||
|
||||
<div class="media-body">
|
||||
<p class="card-text">
|
||||
Patrons get access to several perks, such as behind the scenes videos, posts, and updates.
|
||||
Patrons also get access to a Crafty Controller PE (Patreon Edition) with additional functions not in other versions of Crafty.
|
||||
Patrons get access to several perks, such as behind the scenes videos, posts, and updates.<br>
|
||||
Patrons also get early access to new software!
|
||||
</p>
|
||||
<br />
|
||||
<div class="text-center">
|
||||
@ -44,35 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="card-title">One Time Support</h4>
|
||||
|
||||
<div class="media-body">
|
||||
<p class="card-text">
|
||||
Contribute via Paypal, you can contribute any amount, as often or as little as you want.
|
||||
Please understand that while PayPal calls this a "Donation"; this is not a charitable donation and can not be claimed
|
||||
as a charitable donation for tax purposes
|
||||
</p>
|
||||
<br />
|
||||
<div class="text-center">
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_donations" />
|
||||
<input type="hidden" name="business" value="H2HNTLFZAJRXG" />
|
||||
<input type="hidden" name="currency_code" value="USD" />
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
|
||||
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 grid-margin">
|
||||
<div class="col-md-4 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
@ -93,6 +65,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="card-title">More Ways Coming Soon...</h4>
|
||||
|
||||
<div class="media-body">
|
||||
<p class="card-text">
|
||||
Thank you for your interest in contributing to Aracdia Technology's Crafty Controller.
|
||||
We are always thinking of new ways for our community to contribute to this awesome project. <br><br> If you don't see
|
||||
a contribution method that peaks your interest now please check back soon.
|
||||
</p>
|
||||
<br />
|
||||
<div class="text-center">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -64,7 +64,11 @@
|
||||
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
|
||||
{% end %}
|
||||
{% if person['tags'][1] %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% if type(person['tags'][1]) is list %}
|
||||
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% if person['tags'][2] %}
|
||||
{% if type(person['tags'][2]) is list %}
|
||||
@ -75,11 +79,11 @@
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex align-items-start pt-3">
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
Crafty's {{ person['title'] }}<br /><br />
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
{{ person['blurb'] }}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -129,7 +133,11 @@
|
||||
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
|
||||
{% end %}
|
||||
{% if person['tags'][1] %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% if type(person['tags'][1]) is list %}
|
||||
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% if person['tags'][2] %}
|
||||
{% if type(person['tags'][2]) is list %}
|
||||
@ -140,11 +148,11 @@
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex align-items-start pt-3">
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
Crafty's {{ person['title'] }}<br /><br />
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
{{ person['blurb'] }}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -195,7 +203,11 @@
|
||||
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
|
||||
{% end %}
|
||||
{% if person['tags'][1] %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% if type(person['tags'][1]) is list %}
|
||||
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% if person['tags'][2] %}
|
||||
{% if type(person['tags'][2]) is list %}
|
||||
@ -206,11 +218,11 @@
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex align-items-start pt-3">
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
Crafty's {{ person['title'] }}<br /><br />
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
{{ person['blurb'] }}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -232,7 +244,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Patreon Supporters</h4>
|
||||
<p class="card-description"> A huge <code>thank you</code> to our Patreon supporters </p>
|
||||
<p class="card-description"> A huge <code>thank you</code> to our Patreon supporters! | <span style="color: #9365B8">Last Update: {{ data["lastUpdate"] }}</span></p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -241,15 +253,15 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pat in data["patreons"] %}
|
||||
{% for pat in data["patrons"] %}
|
||||
<tr>
|
||||
<td>{{ pat["name"] }}</td>
|
||||
<td>
|
||||
{% if pat["level"] == "sustainer" %}
|
||||
{% if pat["level"] == "Crafty Sustainer" %}
|
||||
<span class="btn btn-sm btn-info mr-2">Sustainer</span>
|
||||
{% elif pat["level"] == "advocate" %}
|
||||
{% elif pat["level"] == "Crafty Advocate" %}
|
||||
<span class="btn btn-sm btn-primary mr-2">Advocate</span>
|
||||
{% elif pat["level"] == "supporter" %}
|
||||
{% elif pat["level"] == "Crafty Supporter" %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">Supporter</span>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-secondary mr-2">Other</span>
|
||||
@ -310,4 +322,4 @@ $( document ).ready(function() {
|
||||
});
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
@ -366,7 +366,7 @@ dialog.init(function(){
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_reload', function (start_error) {
|
||||
webSocket.on('send_start_reload', function () {
|
||||
location.reload()
|
||||
});
|
||||
}
|
||||
|
@ -40,12 +40,12 @@
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<a class="nav-link active" href="/panel/{{ 'add_user' if data['new_user'] else 'edit_user' }}?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<i class="fas fa-cogs"></i>Config</a>
|
||||
</li>
|
||||
{% if data['new_user'] %}
|
||||
{% if not data['new_user'] %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<a class="nav-link" href="/panel/add_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<i class="fas fa-folder-tree"></i>Other</a>
|
||||
</li>
|
||||
{% end %}
|
||||
@ -80,9 +80,13 @@
|
||||
<label class="form-label" for="password1">Repeat Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
|
||||
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="email">Gravatar Email <small class="text-muted ml-1"> - for the profile picture. this is not required. crafty will never make use of user emails. User emails are strictly for Gravatar</small> </label>
|
||||
<input type="email" class="form-control" name="email" id="email" value="{{ data['user']['email'] }}" placeholder="Gravatar Email" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="language">User Language:</label>
|
||||
<select class="form-select" id="language" name="language" form="user_form">
|
||||
<select class="form-select form-control form-control-lg select-css" id="language" name="language" form="user_form">
|
||||
{% for lang in data['languages'] %}
|
||||
<option value="{{lang}}">{{lang}}</option>
|
||||
{% end %}
|
||||
@ -140,7 +144,7 @@
|
||||
<tr class="rounded">
|
||||
<th>Permission Name</th>
|
||||
<th>Authorized ?</th>
|
||||
<th>Quantity</th>
|
||||
<th>Number of Uses Allowed (-1=No Limit)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -183,9 +187,9 @@
|
||||
|
||||
<label for="superuser" class="form-check-label ml-4 mb-4">
|
||||
{% if data['user']['superuser'] %}
|
||||
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" checked="" value="1" disabled >Super User
|
||||
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" checked="" value="1" {{ data['super-disabled'] }} >Super User
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" value="1" disabled >Super User
|
||||
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" {{ data['super-disabled'] }} value="1" >Super User
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
@ -225,7 +229,7 @@
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete User</a><br />
|
||||
<small>You cannot delete a superuser</small>
|
||||
{% else %}
|
||||
<a href="/panel/remove_user?id={{ data['user']['user_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i> Delete User</a>
|
||||
<button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> Delete User</a>
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
@ -246,6 +250,58 @@
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$( ".delete-user" ).click(function() {
|
||||
var file_to_del = $(this).data("file");
|
||||
|
||||
console.log("User to delete is {{ data['user']['username'] }}");
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{% raw translate('usersConfig', 'deleteUser', data['lang']) %}"+"{{ data['user']['username'] }}",
|
||||
message: "{{ translate('usersConfig', 'confirmDelete', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
className: 'btn-outline-danger',
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
location.href="/panel/remove_user?id={{ data['user']['user_id'] }}";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function superConfirm() {
|
||||
if (document.getElementById('superuser').checked){
|
||||
bootbox.confirm({
|
||||
title: "{{ translate('panelConfig', 'superConfirmTitle', data['lang']) }}",
|
||||
message: "{{ translate('panelConfig', 'superConfirm', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fa fa-times"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
className: 'btn-outline-warning',
|
||||
label: '<i class="fa fa-check"></i> {{ translate('serverBackups', 'confirm', data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result == true){
|
||||
return;
|
||||
}else{
|
||||
document.getElementById('superuser').checked = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}else{
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body pt-3 pb-3">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 mr-2">
|
||||
<div class="col-sm-4 mr-2">
|
||||
{% if data['server_stats']['running'] %}
|
||||
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }} ({{ translate('serverStats', 'serverTime', data['lang']) }})</span><br />
|
||||
@ -13,6 +13,8 @@
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span>
|
||||
{% end %}
|
||||
<br>
|
||||
<b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{ data['serverTZ'] }}</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 mr-2">
|
||||
@ -28,7 +30,7 @@
|
||||
<div class="col-sm-3 mr-2">
|
||||
{% if data['server_stats']['version'] != 'False' %}
|
||||
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ data['server_stats']['version'] }} <br />
|
||||
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> {{ data['server_stats']['desc'] }} <br />
|
||||
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ data['server_stats']['desc'] }}</span> <br />
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
|
||||
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
|
||||
@ -45,6 +47,7 @@
|
||||
|
||||
<script src="/static/assets/vendors/moment/moment.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
|
||||
function durationToHumanizedString (duration) {
|
||||
@ -117,5 +120,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
initParser('input_motd', 'input_motd');
|
||||
|
||||
});
|
||||
</script>
|
@ -13,7 +13,7 @@
|
||||
{% end %}
|
||||
{% if data['permissions']['Schedule'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'schedule' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<a class="nav-link {% if data['active_link'] == 'tasks' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
|
@ -45,8 +45,10 @@
|
||||
<a href="/panel/backup_now?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary" onclick="backup_started()">{{ translate('serverBackups', 'backupNow', data['lang']) }}</a>
|
||||
</div>
|
||||
<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']) }}" >
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@ -54,16 +56,6 @@
|
||||
<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="superuser" class="form-check-label ml-4 mb-4">
|
||||
{% if data['backup_config']['auto_enabled'] %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" checked="" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
</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>
|
||||
</form>
|
||||
@ -81,10 +73,9 @@
|
||||
<table class="table table-responsive dataTable" id="backup_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">{{ translate('serverBackups', 'download', data['lang']) }}</th>
|
||||
<th width="10%">{{ translate('serverBackups', 'options', data['lang']) }}</th>
|
||||
<th>{{ translate('serverBackups', 'path', data['lang']) }}</th>
|
||||
<th width="20%">{{ translate('serverBackups', 'size', data['lang']) }}</th>
|
||||
<th width="10%">{{ translate('serverBackups', 'delete', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -95,16 +86,19 @@
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'download', data['lang']) }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ backup['path'] }}</td>
|
||||
<td>{{ backup['size'] }}</td>
|
||||
<td>
|
||||
<br>
|
||||
<br>
|
||||
<button data-file="{{ backup['path'] }}" class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'delete', data['lang']) }}
|
||||
</button>
|
||||
|
||||
<button data-file="{{ backup['path'] }}" class="btn btn-warning restore_button">
|
||||
<i class="fas fa-undo-alt" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'restore', data['lang']) }}
|
||||
</button>
|
||||
</td>
|
||||
<td>{{ backup['path'] }}</td>
|
||||
<td>{{ backup['size'] }}</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
||||
@ -151,16 +145,44 @@
|
||||
|
||||
console.log('Sending Command to delete backup: ' + filename)
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_file?server_id='+id,
|
||||
data: data_to_send,
|
||||
url: '/ajax/del_backup?server_id='+id,
|
||||
data: {
|
||||
file_path: filename,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function restore_backup(filename, id){
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
console.log('Sending Command to restore backup: ' + filename)
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/restore_backup?server_id='+id,
|
||||
data: {
|
||||
zip_file: filename,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
var dialog = bootbox.dialog({
|
||||
message: '<i class="fa fa-spin fa-spinner"></i> {{ translate('serverBackups', 'restoring', data['lang']) }}',
|
||||
closeButton: false
|
||||
});
|
||||
setTimeout(function(){
|
||||
location.href=('/panel/dashboard');
|
||||
}, 15000);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
$("#backup_config_box").hide();
|
||||
@ -203,14 +225,39 @@
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
del_backup(file_to_del, {{ data['server_stats']['server_id']['server_id'] }} );
|
||||
var full_path = '{{ data['backup_path'] }}' + '/' + file_to_del;
|
||||
del_backup(full_path, {{ data['server_stats']['server_id']['server_id'] }} );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( ".restore_button" ).click(function() {
|
||||
var file_to_restore = $(this).data("file");
|
||||
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{{ translate('serverBackups', 'restore', data['lang']) }} "+file_to_restore,
|
||||
message: "{{ translate('serverBackups', 'confirmRestore', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "restore", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
restore_backup(file_to_restore, {{ data['server_stats']['server_id']['server_id'] }} );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
{% include "parts/details_stats.html" %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
@ -43,57 +43,62 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" >
|
||||
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{% if data['super_user'] %}
|
||||
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" >
|
||||
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" required>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" >
|
||||
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" >
|
||||
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc', data['lang']) }}</small> </label>
|
||||
<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']) }}" >
|
||||
<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>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" >
|
||||
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" >
|
||||
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" required>
|
||||
</div>
|
||||
|
||||
{% if data['super_user'] %}
|
||||
<div class="form-group">
|
||||
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}" >
|
||||
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}">
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" >
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }} </small> </label>
|
||||
<input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" required>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" >
|
||||
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" required>
|
||||
</div>
|
||||
|
||||
<div class="form-check-flat">
|
||||
@ -231,23 +236,32 @@ let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
|
||||
function deleteServer (){
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
bootbox.dialog({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteFilesQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
files: {
|
||||
label: "{{ translate('serverConfig', 'yesDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-link',
|
||||
callback: function(){
|
||||
deleteServerFilesE();
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result){
|
||||
},
|
||||
noFiles: {
|
||||
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-outline-danger',
|
||||
callback: function(){
|
||||
deleteServerE()
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
@ -256,18 +270,18 @@ let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
|
||||
return;}
|
||||
else{
|
||||
deleteServerFilesE();
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'cancel', data['lang']) }}",
|
||||
className: 'btn-secondary',
|
||||
callback: function(){
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
{% include "parts/details_stats.html" %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
@ -134,13 +134,15 @@
|
||||
</style>
|
||||
<ul class="tree-view">
|
||||
<li>
|
||||
<div class="tree-caret tree-ctx-item files-tree-title">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{{ translate('serverFiles', 'files', data['lang']) }}
|
||||
<div class="tree-ctx-item" data-path="{{ data['server_stats']['server_id']['path'] }}">
|
||||
<span id="{{ data['server_stats']['server_id']['path'] }}span" class="files-tree-title tree-caret-down root-dir" data-path="{{ data['server_stats']['server_id']['path'] }}" onclick="getToggleMain(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{{ translate('serverFiles', 'files', data['lang']) }}
|
||||
</span>
|
||||
</div>
|
||||
<ul class="tree-nested" id="files-tree">
|
||||
<li>{{ translate('serverFiles', 'error', data['lang']) }}</li>
|
||||
<ul class="tree-nested d-block" id="files-tree">
|
||||
<li><i class="fa fa-spin fa-spinner"></i>{{ translate('serverFiles', 'loadingRecords', data['lang']) }}</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
@ -647,11 +649,14 @@
|
||||
document.getElementById("fileList").innerHTML = list;
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
function getTreeView() {
|
||||
}
|
||||
function getTreeView(event) {
|
||||
|
||||
path = '{{ data['server_stats']['server_id']['path'] }}'
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_tree?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
url: '/ajax/get_tree?id={{ data['server_stats']['server_id']['server_id'] }}&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
@ -661,26 +666,75 @@
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
document.getElementById('files-tree').innerHTML = text;
|
||||
try{
|
||||
document.getElementById(path).innerHTML += text;
|
||||
event.target.parentElement.classList.add("clicked");
|
||||
}catch{
|
||||
document.getElementById('files-tree').innerHTML = text;
|
||||
}
|
||||
|
||||
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
||||
|
||||
setTimeout(function () {setTreeViewContext()}, 1000);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
var toggler = document.getElementsByClassName("tree-caret");
|
||||
var i;
|
||||
function getToggleMain(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
document.getElementById("files-tree").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret");
|
||||
}
|
||||
|
||||
for (i = 0; i < toggler.length; i++) {
|
||||
if (toggler[i].classList.contains('files-tree-title')) continue;
|
||||
toggler[i].addEventListener("click", function caretListener() {
|
||||
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
|
||||
this.classList.toggle("tree-caret-down");
|
||||
function getDirView(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
|
||||
if (document.getElementById(path).classList.contains('clicked')){
|
||||
|
||||
var toggler = document.getElementById(path+"span");
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_dir?id={{ data['server_stats']['server_id']['server_id'] }}&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try{
|
||||
document.getElementById(path+"span").classList.add('tree-caret-down');
|
||||
document.getElementById(path).innerHTML += text;
|
||||
document.getElementById(path).classList.add("clicked");
|
||||
}catch{
|
||||
console.log("Bad")
|
||||
}
|
||||
|
||||
setTimeout(function () {setTreeViewContext()}, 1000);
|
||||
|
||||
var toggler = document.getElementById(path);
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"span").addEventListener("click", function caretListener() {
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setTreeViewContext() {
|
||||
var treeItems = document.getElementsByClassName('tree-ctx-item');
|
||||
@ -697,7 +751,7 @@
|
||||
}
|
||||
$('#renameItem').show();
|
||||
|
||||
var isDir = event.target.classList.contains('tree-folder');
|
||||
var isDir = event.target.classList.contains('files-tree-title');
|
||||
$('#createFile').toggle(isDir);
|
||||
$('#createDir').toggle(isDir);
|
||||
$('#deleteDir').toggle(isDir);
|
||||
@ -708,7 +762,7 @@
|
||||
$('#downloadFile').toggle(isFile);
|
||||
console.log({ 'event.target': event.target, isDir, isFile });
|
||||
|
||||
if(event.target.classList.contains('files-tree-title')) {
|
||||
if(event.target.classList.contains('root-dir')) {
|
||||
$('#createFile').show();
|
||||
$('#createDir').show();
|
||||
$('#renameItem').hide();
|
||||
@ -875,11 +929,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementsByClassName('files-tree-title')[0].addEventListener("click", function caretListener() {
|
||||
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
|
||||
this.classList.toggle("tree-caret-down");
|
||||
});
|
||||
|
||||
getTreeView();
|
||||
setTreeViewContext();
|
||||
|
||||
@ -898,6 +947,8 @@
|
||||
target.classList.add('btn-primary');
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{% end %}
|
224
app/frontend/templates/panel/server_schedule_edit.html
Normal file
@ -0,0 +1,224 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% 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">
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12">
|
||||
{% if data['new_schedule'] == True %}
|
||||
<form class="forms-sample" method="post" action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
|
||||
{% else %}
|
||||
<form class="forms-sample" method="post" action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
|
||||
{% end %}
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="difficulty">Basic / Cron Select<small class="text-muted ml-1"></small> </label><br>
|
||||
<select id="difficulty" name="difficulty" onchange="basicAdvanced(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['difficulty'] }}">
|
||||
<option id="basic" value="basic">Basic</option>
|
||||
<option id="advanced" value="advanced">Advanced</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="server_name">Action<small class="text-muted ml-1"></small> </label><br>
|
||||
<select id="action" name="action" onchange="yesnoCheck(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
|
||||
<option id="start" value="start">Start Server</option>
|
||||
<option id="restart" value="restart">Restart Server</option>
|
||||
<option id="stop" value="stop">Shutdown Server</option>
|
||||
<option id="backup" value="backup">Backup Server</option>
|
||||
<option id="command" value="command">Custon Command</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="ifBasic">
|
||||
<div class="form-group">
|
||||
<label for="server_path">Interval <small class="text-muted ml-1"> - How often you want this task to execute</small> </label>
|
||||
<input type="number" class="form-control" name="interval" id="interval" value="{{ data['schedule']['interval'] }}" placeholder="Interval" required>
|
||||
<br>
|
||||
<br>
|
||||
<select id="interval_type" onchange="ifDays(this);" name="interval_type" class="form-control form-control-lg select-css" value="{{ data['schedule']['interval_type'] }}">
|
||||
<option id = "days" value="days">Days</option>
|
||||
<option id = "hours" value="hours">Hours</option>
|
||||
<option id = "minutes" value="minutes">Minutes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="ifDays" style="display: block;">
|
||||
<div class="form-group">
|
||||
<label for="time">Time <small class="text-muted ml-1"> - What time do you want your task to execute?</small> </label>
|
||||
<input type="time" class="form-control" name="time" id="time" value="{{ data['schedule']['time'] }}" placeholder="Time" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ifYes" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="command">Command <small class="text-muted ml-1"> - What command do you want us to execute? Do not include the '/'</small> </label>
|
||||
<input type="input" class="form-control" name="command" id="command_input" value="{{ data['schedule']['command'] }}" placeholder="Command" required>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ifAdvanced" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="cron">Cron <small class="text-muted ml-1"> - Input your cron string</small> </label>
|
||||
<input type="input" class="form-control" name="cron" id="cron" value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check-flat">
|
||||
<label for="enabled" class="form-check-label ml-4 mb-4">
|
||||
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">Enabled
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<div class="form-check-flat">
|
||||
<label for="one_time" class="form-check-label ml-4 mb-4">
|
||||
<input type="checkbox" class="form-check-input" id="one_time" name="one_time" value="1">Delete After Execution
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('serverConfig', 'save', data['lang']) }}</button>
|
||||
<button type="reset" onclick="location.href=`/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks`" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
|
||||
});
|
||||
|
||||
function yesnoCheck() {
|
||||
if (document.getElementById('action').value == "command") {
|
||||
document.getElementById("ifYes").style.display = "block";
|
||||
document.getElementById("command_input").required = true;
|
||||
} else {
|
||||
document.getElementById("ifYes").style.display = "none";
|
||||
document.getElementById("command_input").required = false;
|
||||
}
|
||||
}
|
||||
function basicAdvanced() {
|
||||
if (document.getElementById('difficulty').value == "advanced") {
|
||||
document.getElementById("ifAdvanced").style.display = "block";
|
||||
document.getElementById("ifBasic").style.display = "none";
|
||||
document.getElementById("interval").required = false;
|
||||
document.getElementById("time").required = false;
|
||||
} else {
|
||||
document.getElementById("ifAdvanced").style.display = "none";
|
||||
document.getElementById("ifBasic").style.display = "block";
|
||||
document.getElementById("interval").required = true;
|
||||
document.getElementById("time").required = true;
|
||||
}
|
||||
}
|
||||
function ifDays() {
|
||||
if (document.getElementById('interval_type').value == "days") {
|
||||
document.getElementById("ifDays").style.display = "block";
|
||||
document.getElementById("time").required = true;
|
||||
} else {
|
||||
document.getElementById("ifDays").style.display = "none";
|
||||
document.getElementById("time").required = false;
|
||||
}
|
||||
}
|
||||
|
||||
function del_task(sch_id, id){
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id,
|
||||
data: {
|
||||
schedule_id: sch_id,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
function startup(){
|
||||
try{
|
||||
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);
|
||||
}catch{
|
||||
console.log("no element named {{ data['schedule']['interval_type'] }}")
|
||||
}
|
||||
try{
|
||||
document.getElementById("{{ data['schedule']['difficulty'] }}").setAttribute('selected', true);
|
||||
}catch{
|
||||
console.log("no element named {{ data['schedule']['difficulty'] }}")
|
||||
}
|
||||
try{
|
||||
document.getElementById("{{ data['schedule']['action'] }}").setAttribute('selected', true);
|
||||
|
||||
}catch{
|
||||
console.log("no element named {{ data['schedule']['action'] }}")
|
||||
}
|
||||
yesnoCheck();
|
||||
basicAdvanced();
|
||||
ifDays();
|
||||
if("{{ data['schedule']['enabled'] }}" == 'True'){
|
||||
document.getElementById('enabled').checked = true;
|
||||
}else{
|
||||
document.getElementById('enabled').checked = false;
|
||||
}
|
||||
if("{{ data['schedule']['one_time'] }}" == 'True'){
|
||||
document.getElementById('one_time').checked = true;
|
||||
}else{
|
||||
document.getElementById('one_time').checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload(startup())
|
||||
</script>
|
||||
|
||||
{% end %}
|
362
app/frontend/templates/panel/server_tasks.html
Normal file
@ -0,0 +1,362 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% 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">
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12" style="overflow-x:auto;">
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-calendar"></i> Scheduled Tasks</h4>
|
||||
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}", data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}", data-placement="bottom"></span>
|
||||
<div><button onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`" class="btn btn-info">Create New Schedule <i class="fas fa-pencil-alt"></i></button></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" style="table-layout:fixed;">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th style="width: 25%; min-width: 50px;">Action</th>
|
||||
<th style="width: 40%; min-width: 50px;">Command</th>
|
||||
<th style="width: 10%; min-width: 50px;">Interval</th>
|
||||
<th style="width: 10%; min-width: 50px;">Start Time</th>
|
||||
<th style="width: 10%; min-width: 50px;">Enabled</th>
|
||||
<th style="width: 10%; min-width: 50px;">Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for schedule in data['schedules'] %}
|
||||
<tr>
|
||||
<td id="{{schedule.action}}" class="action">
|
||||
<p>{{schedule.action}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
|
||||
<p>{{schedule.command}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.interval}}" class="action">
|
||||
{% if schedule.interval != '' %}
|
||||
<p>Every</p>
|
||||
<p>{{schedule.interval}} {{schedule.interval_type}}</p>
|
||||
{% else %}
|
||||
<p>Cron String:</p>
|
||||
<p>{{schedule.cron_string}}</p>
|
||||
{% end %}
|
||||
</td>
|
||||
<td id="{{schedule.start_time}}" class="action">
|
||||
<p>{{schedule.start_time}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.enabled}}" class="action">
|
||||
{% if schedule.enabled %}
|
||||
<span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
</td>
|
||||
<td id="{{schedule.action}}" class="action">
|
||||
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
|
||||
<i class="fas fa-pencil-alt"></i>
|
||||
</button>
|
||||
<br>
|
||||
<br>
|
||||
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" style="table-layout:fixed;">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th style="width: 25%; min-width: 50px;">Action</th>
|
||||
<th style="max-width: 40%; min-width: 50px;">Command</th>
|
||||
<th style="width: 10%; min-width: 50px;">Enabled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for schedule in data['schedules'] %}
|
||||
<tr data-toggle="modal" data-target="#task_details_{{schedule.schedule_id}}">
|
||||
<td id="{{schedule.action}}" class="action">
|
||||
<p>{{schedule.action}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
|
||||
<p>{{schedule.command}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.enabled}}" class="action">
|
||||
{% if schedule.enabled %}
|
||||
<span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Task Details</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul style="list-style: none;">
|
||||
<li id="{{schedule.action}}" class="action" style="border-top: .1em solid gray;">
|
||||
<h4>Action</h4><p>{{schedule.action}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.command}}" class="action" style="border-top: .1em solid gray;">
|
||||
<h4>Command</h4><p>{{schedule.command}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.interval}}" class="action" style="border-top: .1em solid gray;">
|
||||
{% if schedule.interval != '' %}
|
||||
<h4>Interval</h4> <p>Every {{schedule.interval}} {{schedule.interval_type}}</p>
|
||||
{% else %}
|
||||
<h4>Interval</h4> <p>Cron String: {{schedule.cron_string}}</p>
|
||||
{% end %}
|
||||
</li>
|
||||
<li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;">
|
||||
<h4>Start Time</h4> <p>{{schedule.start_time}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
|
||||
{% if schedule.enabled %}
|
||||
<h4>Enabled</h4> <span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<h4>Enabled</h4> <span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
|
||||
<i class="fas fa-pencil-alt"></i> Edit
|
||||
</button>
|
||||
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i> Delete
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.popover-body{
|
||||
color: white !important;;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<style>
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
td::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
td {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
</style>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
$('#schedule_table').DataTable({
|
||||
'order': [4, 'desc']
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
$('#mini_schedule_table').DataTable({
|
||||
'order': [2, 'desc']
|
||||
}
|
||||
);
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = true;
|
||||
});
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
document.getElementById('schedule_table_wrapper').hidden = true;
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = false;
|
||||
}
|
||||
|
||||
});
|
||||
$(window).ready(function(){
|
||||
$('body').click(function(){
|
||||
$('.too_small').popover("hide");
|
||||
});
|
||||
});
|
||||
$(window).resize(function() {
|
||||
// This will execute whenever the window is resized
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
document.getElementById('schedule_table_wrapper').hidden = true;
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = false;
|
||||
}
|
||||
else{
|
||||
$('.too_small').popover("hide");
|
||||
document.getElementById('schedule_table_wrapper').hidden = false;
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = true;
|
||||
} // New width
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
|
||||
});
|
||||
|
||||
function yesnoCheck(that) {
|
||||
if (that.value == "command") {
|
||||
document.getElementById("ifYes").style.display = "block";
|
||||
document.getElementById("command").required = true;
|
||||
} else {
|
||||
document.getElementById("ifYes").style.display = "none";
|
||||
document.getElementById("command").required = false;
|
||||
}
|
||||
}
|
||||
function basicAdvanced(that) {
|
||||
if (that.value == "advanced") {
|
||||
document.getElementById("ifAdvanced").style.display = "block";
|
||||
document.getElementById("ifBasic").style.display = "none";
|
||||
document.getElementById("interval").required = false;
|
||||
document.getElementById("time").required = false;
|
||||
} else {
|
||||
document.getElementById("ifAdvanced").style.display = "none";
|
||||
document.getElementById("ifBasic").style.display = "block";
|
||||
document.getElementById("interval").required = true;
|
||||
document.getElementById("time").required = true;
|
||||
}
|
||||
}
|
||||
function ifDays(that) {
|
||||
if (that.value == "days") {
|
||||
document.getElementById("ifDays").style.display = "block";
|
||||
document.getElementById("time").required = true;
|
||||
} else {
|
||||
document.getElementById("ifDays").style.display = "none";
|
||||
document.getElementById("time").required = false;
|
||||
}
|
||||
}
|
||||
|
||||
$( ".del_button" ).click(function() {
|
||||
var sch_id = $(this).data('sch');
|
||||
var server_id = {{ data['server_stats']['server_id']['server_id'] }};
|
||||
|
||||
console.log(sch_id)
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{{ translate('serverSchedules', 'areYouSure', data['lang']) }}",
|
||||
message: "{{ translate('serverSchedules', 'confirmDelete', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverSchedules", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
className: 'btn-outline-danger',
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverSchedules", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
del_task(sch_id, server_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function del_task(sch_id, id){
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id,
|
||||
data: {
|
||||
schedule_id: sch_id,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -43,7 +43,6 @@
|
||||
<div style="gap: 0.5rem;" class="input-group flex-wrap">
|
||||
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" autofocus="">
|
||||
<span class="input-group-btn ml-5">
|
||||
<input type="hidden" value="" id="last_command"/>
|
||||
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand', data['lang']) }}</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -127,13 +126,17 @@
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
if (updateButton.isUpdating){
|
||||
if(updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
|
||||
console.log(updateButton.isUpdating)
|
||||
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data['lang']) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
|
||||
window.location.reload()
|
||||
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data['lang']) }}</button><button onclick="send_command(server_id, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Convert running to lower case (example: 'True' converts to 'true') and
|
||||
@ -202,10 +205,13 @@
|
||||
$(this).removeAttr("disabled"); //Enable the textbox again if needed.
|
||||
$(this).focus();
|
||||
}
|
||||
else if (e.which == 38){
|
||||
last_command = $('#last_command').val()
|
||||
$("#server_command").val(last_command)
|
||||
}
|
||||
else if (e.which == 38) {
|
||||
e.preventDefault();
|
||||
$('#server_command').val(cmdHistory.getPrev());
|
||||
} else if (e.which == 40) {
|
||||
e.preventDefault();
|
||||
$('#server_command').val(cmdHistory.getNext());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -225,7 +231,8 @@
|
||||
function send_command_to_server(){
|
||||
var server_command = $("#server_command").val()
|
||||
console.log(server_command)
|
||||
$("#last_command").val(server_command)
|
||||
|
||||
cmdHistory.push(server_command);
|
||||
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
@ -244,6 +251,31 @@
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const cmdHistory = {
|
||||
history: [],
|
||||
current: 0,
|
||||
push: function(cmd) {
|
||||
this.history.push(cmd);
|
||||
this.current = this.history.length - 1;
|
||||
},
|
||||
getPrev: function() {
|
||||
const prevCommand = this.history[this.current];
|
||||
this.current--;
|
||||
if (this.current < 0) this.current = 0;
|
||||
return prevCommand;
|
||||
},
|
||||
getNext: function() {
|
||||
this.current++;
|
||||
if (this.current > (this.history.length - 1)) {
|
||||
this.current = (this.history.length - 1);
|
||||
return '';
|
||||
}
|
||||
const nextCommand = this.history[this.current];
|
||||
return nextCommand;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -72,6 +72,11 @@
|
||||
<div class="form-group">
|
||||
<button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login', data['lang']) }}</button>
|
||||
</div>
|
||||
{% if error_msg is not None %}
|
||||
<fieldset style="color: red; text-align: center;">
|
||||
<span>{{error_msg}}</span>
|
||||
</fieldset>
|
||||
{% end %}
|
||||
<div class="form-group d-flex justify-content-between">
|
||||
<div class="form-check form-check-flat mt-0">
|
||||
|
||||
|
@ -39,7 +39,7 @@
|
||||
{% else %}
|
||||
<img src="/static/assets/images/pack.png" alt="icon" />
|
||||
{% end %}
|
||||
<span id="input_motd">{{ server['stats']['desc'] }}</span> <br />
|
||||
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{ server['stats']['desc'] }}</span> <br />
|
||||
{% end %}
|
||||
</td>
|
||||
<td>
|
||||
@ -73,9 +73,12 @@
|
||||
{% block js %}
|
||||
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
initParser('input_motd', 'input_motd');
|
||||
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
|
||||
for (element of all_motds) {
|
||||
initParser(element.id, element.id);
|
||||
};
|
||||
}());
|
||||
</script>
|
||||
|
||||
|
@ -1,327 +1,639 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverWizard', 'newServer', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard">
|
||||
{% raw xsrf_form_html() %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_type">{{ translate('serverWizard', 'serverType', data['lang']) }}</label>
|
||||
<select class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)">
|
||||
{% for s in data['server_types'] %}
|
||||
<option value="{{ s }}">{{ s.capitalize() }}</option>
|
||||
{% end %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label>
|
||||
<select class="form-control form-control-lg select-css" id="server" name="server">
|
||||
<option value="0">Select a Version</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg()">{{ translate('serverWizard', 'buildServer', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_jar" name="create_type">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory2" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory2" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-13 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_zip" name="create_type">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-sm-3">
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js%}
|
||||
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
console.log('ready');
|
||||
var forms = $('form.server-wizard');
|
||||
forms.each(function(i, formEl) {
|
||||
var form = $(formEl);
|
||||
var min = form.find('[name=min_memory]');
|
||||
var max = form.find('[name=max_memory]');
|
||||
console.log(form, min, max)
|
||||
min.change(function(){
|
||||
check_sizes(max, min, 'min');
|
||||
});
|
||||
max.change(function(){
|
||||
check_sizes(max, min, 'max');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function wait_msg(importing){
|
||||
bootbox.alert({
|
||||
title: importing ? '{% raw translate("serverWizard", "importing", data['lang']) %}' : '{% raw translate("serverWizard", "downloading", data['lang']) %}',
|
||||
message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data['lang']) %}'
|
||||
});
|
||||
}
|
||||
|
||||
function check_sizes(a, b, changed){
|
||||
max_mem = parseFloat(a.val());
|
||||
min_mem = parseFloat(b.val());
|
||||
if (max_mem < min_mem && changed === 'min'){
|
||||
a.val(min_mem)
|
||||
}
|
||||
if (max_mem < min_mem && changed === 'max'){
|
||||
b.val(max_mem)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
// array of possible countries in the same order as they appear in the country selection list
|
||||
|
||||
function decodeHtmlCharCodes(str) {
|
||||
return str.replace(""", "\"");
|
||||
}
|
||||
|
||||
function convertHtmlJsonToJavacriptArray(str) {
|
||||
var result = []
|
||||
str = decodeHtmlCharCodes(str)
|
||||
for(var i in str)
|
||||
result.push([i, str [i]]);
|
||||
return result
|
||||
}
|
||||
|
||||
var text = '{% raw data["js_server_types"] %}';
|
||||
var serverTypesLists = JSON.parse(text);
|
||||
//convertHtmlJsonToJavacriptArray('{{ data["js_server_types"] }}')
|
||||
/* CountryChange() is called from the onchange event of a select element.
|
||||
* param selectObj - the select object which fired the on change event.
|
||||
*/
|
||||
function serverTypeChange(selectObj) {
|
||||
// get the index of the selected option
|
||||
var idx = selectObj.selectedIndex;
|
||||
// get the value of the selected option
|
||||
var which = selectObj.options[idx].value;
|
||||
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
||||
cList = serverTypesLists[which];
|
||||
// get the country select element via its known id
|
||||
var cSelect = document.getElementById("server");
|
||||
// remove the current options from the country select
|
||||
var len=cSelect.options.length;
|
||||
while (cSelect.options.length > 0) {
|
||||
cSelect.remove(0);
|
||||
}
|
||||
var newOption;
|
||||
// create new options
|
||||
for (var i=0; i<cList.length; i++) {
|
||||
newOption = document.createElement("option");
|
||||
newOption.value = which+"|"+cList[i]; // assumes option string and value are the same
|
||||
newOption.text=cList[i];
|
||||
// add the new option
|
||||
try {
|
||||
cSelect.add(newOption); // this will fail in DOM browsers but is needed for IE
|
||||
}
|
||||
catch (e) {
|
||||
cSelect.appendChild(newOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverWizard', 'newServer', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
<div class="d-none" id="overlay" onclick="hide(event)"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard" onSubmit="wait_msg()">
|
||||
{% raw xsrf_form_html() %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_type">{{ translate('serverWizard', 'serverType', data['lang']) }}</label>
|
||||
<select required class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)">
|
||||
<option value="">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
|
||||
{% for s in data['server_types'] %}
|
||||
<option value="{{ s }}">{{ s.capitalize() }}</option>
|
||||
{% end %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label>
|
||||
<select required class="form-control form-control-lg select-css" id="server" name="server">
|
||||
<option value="">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" min="0.5" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5" min="0.5" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-1">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-1">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" aria-controls="collapseRole-1">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'buildServer', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_jar" name="create_type">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory2" name="min_memory" value="1" step="0.5" min="0.5" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory2" name="max_memory" value="2" step="0.5" min="0.5" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-2">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-2">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-2" class="collapse" aria-labelledby="Role-2" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-13 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_zip" name="create_type">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{ translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
|
||||
<br>
|
||||
<button class="btn btn-primary mr-2" id="root_files_button" type="button">{{ translate('serverWizard', 'clickRoot', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-sm-3">
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" step="0.5" min="0.5" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" step="0.5" min="0.5" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-3">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-3">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12" style="visibility: hidden;">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
|
||||
</div>
|
||||
</div>
|
||||
<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('serverWizard', 'selectZipDir', 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%;">
|
||||
<input type="radio" id="main-tree-input" name="root_path" value="" checked>
|
||||
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{{ translate('serverFiles', 'files', data['lang']) }}
|
||||
</span>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverWizard', 'close', 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>
|
||||
</div>
|
||||
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.scroll {
|
||||
max-height: 12em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.menu-btn {
|
||||
font-size: 0.9em;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
.menu {
|
||||
padding-top: 10px;
|
||||
z-index: 200;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
background-color: #2a2c44;
|
||||
}
|
||||
.menu-option {
|
||||
padding: 6px 20px 6px;
|
||||
color: white;
|
||||
}
|
||||
#overlay {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
/* Remove default bullets */
|
||||
.tree-view,
|
||||
.tree-nested {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Style the items */
|
||||
.tree-item,
|
||||
.files-tree-title {
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
}
|
||||
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
.tree-caret .fa-folder {
|
||||
display: inline-block;
|
||||
}
|
||||
.tree-caret .fa-folder-open {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||
.tree-caret-down .fa-folder {
|
||||
display: none;
|
||||
}
|
||||
.tree-caret-down .fa-folder-open {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Hide the nested list */
|
||||
.tree-nested {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js%}
|
||||
<script>
|
||||
document.getElementById("root_files_button").addEventListener("click", function(){
|
||||
if(document.forms["zip"]["server_path"].value != ""){
|
||||
if(document.getElementById('root_files_button').classList.contains('clicked')){
|
||||
document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input" name="root_path" value="" checked><span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate('serverFiles', 'files', data['lang']) }}</span></input>'
|
||||
}else{
|
||||
document.getElementById('root_files_button').classList.add('clicked')
|
||||
}
|
||||
path = document.forms["zip"]["server_path"].value;
|
||||
console.log(document.forms["zip"]["server_path"].value)
|
||||
var token = getCookie("_xsrf");
|
||||
var dialog = bootbox.dialog({
|
||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||
closeButton: false
|
||||
});
|
||||
setTimeout(function(){
|
||||
dialog.modal('hide');
|
||||
}, 2000);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/unzip_server?id=-1&path='+path,
|
||||
});
|
||||
}else{
|
||||
bootbox.alert("You must input a path before selecting this button");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function dropDown(event) {
|
||||
event.target.parentElement.children[1].classList.remove("d-none");
|
||||
document.getElementById("overlay").classList.remove("d-none");
|
||||
}
|
||||
function hide(event) {
|
||||
var items = document.getElementsByClassName('menu');
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].classList.add("d-none");
|
||||
}
|
||||
document.getElementById("overlay").classList.add("d-none");
|
||||
}
|
||||
$( document ).ready(function() {
|
||||
console.log('ready');
|
||||
var forms = $('form.server-wizard');
|
||||
forms.each(function(i, formEl) {
|
||||
var form = $(formEl);
|
||||
var min = form.find('[name=min_memory]');
|
||||
var max = form.find('[name=max_memory]');
|
||||
console.log(form, min, max)
|
||||
min.change(function(){
|
||||
check_sizes(max, min, 'min');
|
||||
});
|
||||
max.change(function(){
|
||||
check_sizes(max, min, 'max');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function wait_msg(importing){
|
||||
bootbox.alert({
|
||||
title: importing ? '{% raw translate("serverWizard", "importing", data['lang']) %}' : '{% raw translate("serverWizard", "downloading", data['lang']) %}',
|
||||
message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data['lang']) %}',
|
||||
});
|
||||
}
|
||||
|
||||
function show_file_tree(){
|
||||
$("#dir_select").modal();
|
||||
}
|
||||
|
||||
function check_sizes(a, b, changed){
|
||||
max_mem = parseFloat(a.val());
|
||||
min_mem = parseFloat(b.val());
|
||||
if (max_mem < min_mem && changed === 'min'){
|
||||
a.val(min_mem)
|
||||
}
|
||||
if (max_mem < min_mem && changed === 'max'){
|
||||
b.val(max_mem)
|
||||
}
|
||||
}
|
||||
|
||||
function getTreeView(path) {
|
||||
document.getElementById('zip_submit').disabled = false;
|
||||
path = path
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_zip_tree?id=-1&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try{
|
||||
document.getElementById('main-tree-div').innerHTML += text;
|
||||
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||
}catch{
|
||||
document.getElementById('files-tree').innerHTML = text;
|
||||
}
|
||||
|
||||
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getToggleMain(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
document.getElementById("files-tree").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret");
|
||||
}
|
||||
|
||||
|
||||
function getDirView(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
|
||||
if (document.getElementById(path).classList.contains('clicked')){
|
||||
|
||||
var toggler = document.getElementById(path+"span");
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_zip_dir?id=-1&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try{
|
||||
document.getElementById(path+"span").classList.add('tree-caret-down');
|
||||
document.getElementById(path).innerHTML += text;
|
||||
document.getElementById(path).classList.add("clicked");
|
||||
}catch{
|
||||
console.log("Bad")
|
||||
}
|
||||
|
||||
var toggler = document.getElementById(path);
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"span").addEventListener("click", function caretListener() {
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
if (webSocket) {
|
||||
webSocket.on('send_temp_path', function (data) {
|
||||
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
||||
getTreeView(data.path);
|
||||
show_file_tree();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
// array of possible countries in the same order as they appear in the country selection list
|
||||
|
||||
function decodeHtmlCharCodes(str) {
|
||||
return str.replace(""", "\"");
|
||||
}
|
||||
|
||||
function convertHtmlJsonToJavacriptArray(str) {
|
||||
var result = []
|
||||
str = decodeHtmlCharCodes(str)
|
||||
for(var i in str)
|
||||
result.push([i, str [i]]);
|
||||
return result
|
||||
}
|
||||
|
||||
var text = '{% raw data["js_server_types"] %}';
|
||||
var serverTypesLists = JSON.parse(text);
|
||||
//convertHtmlJsonToJavacriptArray('{{ data["js_server_types"] }}')
|
||||
/* CountryChange() is called from the onchange event of a select element.
|
||||
* param selectObj - the select object which fired the on change event.
|
||||
*/
|
||||
function serverTypeChange(selectObj) {
|
||||
// get the index of the selected option
|
||||
var idx = selectObj.selectedIndex;
|
||||
// get the value of the selected option
|
||||
var which = selectObj.options[idx].value;
|
||||
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
||||
cList = serverTypesLists[which];
|
||||
// get the country select element via its known id
|
||||
var cSelect = document.getElementById("server");
|
||||
// remove the current options from the country select
|
||||
var len=cSelect.options.length;
|
||||
while (cSelect.options.length > 0) {
|
||||
cSelect.remove(0);
|
||||
}
|
||||
var newOption;
|
||||
// create new options ordered by descending
|
||||
for (var i=(cList.length)-1; i>=0; i--) {
|
||||
newOption = document.createElement("option");
|
||||
newOption.value = which+"|"+cList[i]; // assumes option string and value are the same
|
||||
newOption.text=cList[i];
|
||||
// add the new option
|
||||
try {
|
||||
cSelect.add(newOption); // this will fail in DOM browsers but is needed for IE
|
||||
}
|
||||
catch (e) {
|
||||
cSelect.appendChild(newOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
{% end %}
|
17
app/migrations/20210915205501_backup_schedule.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
from app.classes.models.management import Schedules
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.drop_columns('backups', ['schedule_id'])
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.add_columns('backups', schedule_id=peewee.ForeignKeyField(Schedules, backref='backups_schedule'))
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_cron_task.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('schedules', cron_string=peewee.CharField(default=""))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('schedules', ['cron_string'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_first_run_.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('server_stats', first_run=peewee.BooleanField(default=True))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('server_stats', ['first_run'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_one_time_task.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('schedules', one_time=peewee.BooleanField(default=False))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('schedules', ['one_time'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_user_email.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('users', email=peewee.CharField(default="default@example.com"))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('users', ['email'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_users_log_path.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('users', support_logs=peewee.CharField(default=""))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('users', ['support_logs'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
346
app/translations/de_DE.json
Normal file
@ -0,0 +1,346 @@
|
||||
{
|
||||
"login": {
|
||||
"forgotPassword": "Passwort vergessen",
|
||||
"login": "Einloggen",
|
||||
"password": "Passwort",
|
||||
"username": "Nutzername"
|
||||
},
|
||||
"error": {
|
||||
"hereIsTheError": "Das ist der Fehler",
|
||||
"contact": "Kontaktieren Sie den Crafty Control Support über Discord",
|
||||
"terribleFailure": "Was für ein furchtbarer Fehler!",
|
||||
"embarassing": "Oh je, das ist peinlich.",
|
||||
"error": "Error!",
|
||||
"start-error": "Der Server {} konnte wegen dem Fehlercode: {} nicht gestartet werden",
|
||||
"closedPort": "Wir haben festgestellt, dass der Port {} im Hostnetzwerk möglicherweise nicht geöffnet ist oder von einer Firewall blockiert wird. Remote-Client-Verbindungen zum Server können dadurch eingeschränkt sein.",
|
||||
"internet": "Wir haben festgestellt, dass der Rechner, auf dem Crafty läuft, keine Verbindung zum Internet hat. Client-Verbindungen zum Server können dadurch eingeschränkt sein.",
|
||||
"eulaTitle": "Der EULA zustimmen",
|
||||
"eulaMsg": "Sie müssen der EULA zustimmen. Eine Kopie der Mojang EULA ist unter dieser Nachricht verlinkt.",
|
||||
"eulaAgree": "Stimmen Sie zu?"
|
||||
},
|
||||
"404": {
|
||||
"contact": "Kontaktieren Sie den Crafty Control Support über Discord",
|
||||
"unableToFind": "Die von Ihnen gesuchte Seite konnte nicht gefunden werden. Bitte versuchen Sie es erneut, oder gehen Sie zurück und aktualisieren Sie.",
|
||||
"notFound": "Seite nicht gefunden"
|
||||
},
|
||||
"footer": {
|
||||
"version": "Version",
|
||||
"copyright": "Urheberrecht",
|
||||
"allRightsReserved": "Alle Rechte vorbehalten"
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "Dashboard",
|
||||
"servers": "Servers",
|
||||
"documentation": "Documentation",
|
||||
"credits": "Credits",
|
||||
"contribute": "Contribute",
|
||||
"newServer": "Create New Server",
|
||||
"navigation": "Navigation"
|
||||
},
|
||||
"serverWizard": {
|
||||
"newServer": "Neuen Server erstellen",
|
||||
"importServer": "Existierenden Server importieren",
|
||||
"importZip": "Import aus einer zip Datei",
|
||||
"serverName": "Server Name",
|
||||
"serverPath": "Server Pfad",
|
||||
"serverType": "Server Typ",
|
||||
"selectType": "Typ auswählen",
|
||||
"serverVersion": "Server Version",
|
||||
"selectVersion": "Version auswählen",
|
||||
"absoluteServerPath": "Absoluter Pfad zu dem Server",
|
||||
"serverJar": "Server Jar",
|
||||
"minMem": "Minimaler RAM",
|
||||
"maxMem": "Maximaler RAM",
|
||||
"serverPort": "Server Port",
|
||||
"defaultPort": "25565 (Standart)",
|
||||
"sizeInGB": "Größe in GB",
|
||||
"zipPath": "Server Pfad",
|
||||
"absoluteZipPath": "Absoluter Pfad zum Server",
|
||||
"resetForm": "Konfiguration zurücksetzen",
|
||||
"importServerButton": "Server importieren!",
|
||||
"buildServer": "Server erstellen!",
|
||||
"quickSettings": "Schnelleinstellungen",
|
||||
"quickSettingsDescription": "Keine Sorge, die Einstelungen können später geändert werden",
|
||||
"myNewServer": "Neuer Server",
|
||||
"bePatient": "Bitte warten, während wir den Server Importieren ' + (importing ? 'import' : 'download')",
|
||||
"importing": "Server importieren...",
|
||||
"downloading": "Server herunterladen...",
|
||||
"addRole": "Server zu existierender Rolle hinzufügen",
|
||||
"autoCreate": "Wenn keine ausgewählt werden wird Crafty automatishce eine erstellen",
|
||||
"selectRole": "Rolle(n) auswählen"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Dashboard",
|
||||
"memUsage": "RAM/Speicherverbrauch",
|
||||
"cpuUsage": "CPU-Nutzung",
|
||||
"host": "Host",
|
||||
"players": "Spieler",
|
||||
"backups": "Backups",
|
||||
"newServer": "Neuen Server erstellen",
|
||||
"allServers": "Alle Server",
|
||||
"server": "Server",
|
||||
"actions": "Aktionen",
|
||||
"world": "Welt",
|
||||
"motd": "MOTD",
|
||||
"version": "Version",
|
||||
"status": "Status",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"lastBackup": "Letzes:",
|
||||
"nextBackup": "Nächstes:",
|
||||
"servers": "Server",
|
||||
"cannotSeeOnMobile": "Sehen Sie nicht alles auf dem Handy?",
|
||||
"cannotSee": "Sehen Sie nicht alles?",
|
||||
"cannotSeeOnMobile2": "Versuchen Sie, die Tabelle seitlich zu verschieben.",
|
||||
"max": "Max",
|
||||
"avg": "durschnittlich",
|
||||
"bePatientStart": "Bitte haben Sie etwas Geduld, während wir den Server starten.<br /> Dieser Bildschirm wird in einem Moment aktualisiert",
|
||||
"bePatientStop": "Bitte haben Sie etwas Geduld, während wir den Server stoppen.<br /> Dieser Bildschirm wird in einem Moment aktualisiert",
|
||||
"bePatientRestart": "Bitte haben Sie etwas Geduld, während wir den Server neu starten.<br /> Dieser Bildschirm wird in einem Moment aktualisiert",
|
||||
"bePatientClone": "Bitte haben Sie etwas Geduld, während wir den Server klonen.<br /> Dieser Bildschirm wird in einem Moment aktualisiert",
|
||||
"sendingCommand": "Übermittlung Ihres Befehls",
|
||||
"cpuCurFreq": "Momentaner CPU-takt",
|
||||
"cpuMaxFreq": "Maximaler CPU-takt",
|
||||
"cpuCores": "CPU Kerne",
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"clone": "Klonen",
|
||||
"kill": "Prozess beenden",
|
||||
"restart": "Neustart",
|
||||
"killing": "Beende Prozess...",
|
||||
"starting": "Verzögerter Start",
|
||||
"delay-explained": "Der Dienst wurde kürzlich gestartet und verzögert den Start der Minecraft-Serverinstanz",
|
||||
"no-servers": "Derzeit sind keine Server vorhanden. Um loszulegen, klicken Sie",
|
||||
"welcome": "Willkommen bei Crafty Controller"
|
||||
},
|
||||
"accessDenied": {
|
||||
"accessDenied": "Zugriff Verweigert",
|
||||
"noAccess": "Sie haben keinen Zugang zu dieser Ressource",
|
||||
"contactAdmin": "Wenden Sie sich an Ihren Serveradministrator, um Zugang zu dieser Ressource zu erhalten, oder wenden Sie sich an den Support, wenn Sie der Meinung sind, dass Sie Zugriff auf diese Ressource haben sollten.",
|
||||
"contact": "Kontaktieren Sie den Crafty Control Support über Discord"
|
||||
},
|
||||
"serverStats": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"serverStatus": "Server Status",
|
||||
"serverStarted": "Server gestartet",
|
||||
"serverUptime": "Server-Betriebszeit",
|
||||
"players": "Spieler",
|
||||
"memUsage": "RAM/Speicherverbrauch",
|
||||
"cpuUsage": "CPU-Nutzung",
|
||||
"version": "Version",
|
||||
"description": "Beschreibung",
|
||||
"errorCalculatingUptime": "Fehler bei der Berechnung der Betriebszeit",
|
||||
"serverTime": "UTC-Zeit",
|
||||
"unableToConnect": "Verbindung nicht möglich"
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "Server Details",
|
||||
"terminal": "Terminal",
|
||||
"logs": "Protokolle",
|
||||
"schedule": "Zeitplan",
|
||||
"backup": "Backup",
|
||||
"files": "Dateien",
|
||||
"config": "Config",
|
||||
"playerControls": "Spieler-Management"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "Automatisches Scrollen stoppen",
|
||||
"commandInput": "Geben Sie Ihren Befehl ein",
|
||||
"sendCommand": "Befehl senden",
|
||||
"start": "Start",
|
||||
"restart": "Neustart",
|
||||
"stop": "Stop",
|
||||
"updating": "Aktualisierung...",
|
||||
"starting": "Verzögertes Starten",
|
||||
"delay-explained": "Der Dienst wurde kürzlich gestartet und verzögert den Start der Minecraft-Serverinstanz"
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "Spieler",
|
||||
"bannedPlayers": "Gebannte Spieler",
|
||||
"loadingBannedPlayers": "Lade Gebannte Spieler"
|
||||
},
|
||||
"serverBackups": {
|
||||
"backupNow": "Jetzt sichern!",
|
||||
"backupAtMidnight": "Auto-backup um 24:00 Uhr?",
|
||||
"storageLocation": "Speicherort",
|
||||
"storageLocationDesc": "Wo wollen Sie die Backups speichern?",
|
||||
"maxBackups": "Max Backups",
|
||||
"maxBackupsDesc": "Crafty speichert nicht mehr als N Backups, wodurch das älteste gelöscht wird (geben Sie 0 ein, um alle zu behalten)",
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"currentBackups": "Aktuelle Backups",
|
||||
"download": "Herunterladen",
|
||||
"path": "Pfad",
|
||||
"size": "Größe",
|
||||
"delete": "Löschen",
|
||||
"backupTask": "Ein Backup-Auftrag wurde gestartet.",
|
||||
"destroyBackup": "Backup löschen \" + file_to_del + \"?",
|
||||
"confirmDelete": "Möchten Sie diese Backup-Datei löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"confirm": "Bestätigen",
|
||||
"options": "Optionen"
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "Der Dateimanager funktioniert nicht ohne JavaScript",
|
||||
"error": "Fehler beim Abrufen von Dateien",
|
||||
"files": "Dateien",
|
||||
"default": "Standart",
|
||||
"save": "Speichern",
|
||||
"editingFile": "Bearbeitung der Datei",
|
||||
"delete": "Löschen",
|
||||
"createFile": "Datei erstellen",
|
||||
"createDir": "Verzeichnis erstellen",
|
||||
"rename": "Umbenennen",
|
||||
"createFileQuestion": "Welchen Namen wünschen Sie für die neue Datei?",
|
||||
"createDirQuestion": "Welchen Namen wünschen Sie für das neue Verzeichnis?",
|
||||
"renameItemQuestion": "Wie soll der neue Name lauten?",
|
||||
"deleteItemQuestion": "Sind Sie sicher, dass Sie \" + name + \" löschen wollen?",
|
||||
"deleteItemQuestionMessage": "Sie löschen gerade \\\"\" + path + \"\\\"!<br/><br/> Diese Aktion ist unumkehrbar!",
|
||||
"yesDelete": "Ja, unumkehrbar löschen",
|
||||
"noDelete": "Nein, nicht löschen",
|
||||
"unsupportedLanguage": "Warnung: Dies ist ein nicht unterstützter Dateityp",
|
||||
"keybindings": "Tastenkombinationen",
|
||||
"fileReadError": "Datei-Lesefehler",
|
||||
"upload": "Hochladen",
|
||||
"unzip": "Entpacken",
|
||||
"clickUpload": "Klicken Sie hier, um Ihre Dateien auszuwählen",
|
||||
"uploadTitle": "Dateien hochladen in: ",
|
||||
"waitUpload": "Bitte warten Sie, während wir Ihre Dateien hochladen... Dies kann eine Weile dauern.",
|
||||
"stayHere": "VERLASSEN SIE DIESE SEITE NICHT!",
|
||||
"close": "Schließen",
|
||||
"download": "Herunterladen"
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "Server Name",
|
||||
"serverNameDesc": "Wie möchten Sie diesen Server nennen",
|
||||
"serverPath": "Server-Arbeitsverzeichnis",
|
||||
"serverPathDesc": "Absoluter vollständiger Pfad (ohne die ausführbare Datei)",
|
||||
"serverLogLocation": "Server Log Speicherort",
|
||||
"serverLogLocationDesc": "Absoluter vollständiger Pfad zur Protokolldatei",
|
||||
"serverExecutable": "Server Ausführbare Datei",
|
||||
"serverExecutableDesc": "Die ausführbare Datei des Servers",
|
||||
"serverExecutionCommand": "Server Ausführungsbefehl",
|
||||
"serverExecutionCommandDesc": "Was wird in einem versteckten Terminal gestartet",
|
||||
"serverStopCommand": "Server Stop Befehl",
|
||||
"serverStopCommandDesc": "Befehl an das Programm, um es zu stoppen",
|
||||
"serverAutostartDelay": "Server Autostart-Verzögerung",
|
||||
"serverAutostartDelayDesc": "Verzögerung vor dem automatischen Start (falls unten aktiviert)",
|
||||
"serverIP": "Server IP",
|
||||
"serverIPDesc": "Die IP, mit der sich Crafty für Statistiken verbinden soll (versuchen Sie eine echte IP anstelle von 127.0.0.1, wenn Sie Probleme haben)",
|
||||
"serverPort": "Server Port",
|
||||
"serverPortDesc": "Der Port, mit dem sich Crafty für Statistiken verbinden soll",
|
||||
"removeOldLogsAfter": "Alte Logs entfernen nach",
|
||||
"removeOldLogsAfterDesc": "Wie viele Tage muss eine Protokolldatei alt sein, um gelöscht zu werden (0 heißt, sie wird nicht gelöscht)",
|
||||
"serverAutoStart": "Server Automatisch starten",
|
||||
"serverCrashDetection": "Erkennung von Serverabstürzen",
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"deleteServer": "Server löschen",
|
||||
"stopBeforeDeleting": "Bitte stoppen Sie den Server, bevor Sie ihn löschen",
|
||||
"exeUpdateURLDesc": "Direkte Download-URL für Updates.",
|
||||
"exeUpdateURL": "Server Ausführbare Update URL",
|
||||
"update": "Server Datei aktualisieren",
|
||||
"bePatientUpdate": "Bitte haben Sie etwas Geduld, während wir den Server aktualisieren. Die Downloadzeiten können je nach Ihrer Internetgeschwindigkeit variieren.<br /> Dieser Bildschirm wird sich in einem Moment aktualisieren",
|
||||
"sendingRequest": "Ihre Anfrage senden...",
|
||||
"deleteServerQuestion": "Server löschen?",
|
||||
"deleteServerQuestionMessage": "Sind Sie sicher, dass Sie diesen Server löschen wollen? Danach gibt es kein Zurück mehr...",
|
||||
"yesDelete": "Ja, löschen",
|
||||
"noDelete": "Nein, zurück",
|
||||
"deleteFilesQuestion": "Serverdateien vom Rechner löschen?",
|
||||
"deleteFilesQuestionMessage": "Möchten Sie, dass Crafty alle Serverdateien auf dem Hostrechner löscht?",
|
||||
"yesDeleteFiles": "Ja, Dateien löschen",
|
||||
"noDeleteFiles": "Nein, nur aus dem Panel entfernen",
|
||||
"sendingDelete": "Lösche den Server",
|
||||
"bePatientDelete": "Bitte haben Sie etwas Geduld, während wir Ihren Server aus dem Crafty-Panel entfernen. Dieser Bildschirm wird in wenigen Augenblicken geschlossen.",
|
||||
"bePatientDeleteFiles" : "Bitte haben Sie etwas Geduld, während wir Ihren Server aus dem Crafty-Panel entfernen und alle Dateien löschen. Dieser Bildschirm wird in wenigen Augenblicken geschlossen."
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Server-Konfigurationsbereich",
|
||||
"desc": "Hier können Sie die Konfiguration Ihres Servers ändern",
|
||||
"perms": [
|
||||
"Es wird empfohlen, <code>NICHT</code> die Pfade eines von Crafty verwalteten Servers zu ändern.",
|
||||
"Das Ändern von Pfaden <code>kann</code> Dinge kaputt machen, besonders auf Linux-Betriebssystemen, wo die Dateiberechtigungen strenger geregelt sind.",
|
||||
"<br /><br/>",
|
||||
"Wenn Sie meinen, dass Sie den Speicherort eines Servers ändern müssen, können Sie dies tun, solange Sie dem Benutzer \"crafty\" die Berechtigung geben, in dem Serverpfad zu lesen/schreiben.",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"Unter Linux geschieht dies am besten mit folgendem Befehl:<br />",
|
||||
"<code>",
|
||||
" sudo chown crafty:crafty /pfad/zu/ihrem/server -R<br />",
|
||||
" sudo chmod 2775 /pfad/zu/ihrem/server -R<br />",
|
||||
"</code>"
|
||||
]
|
||||
},
|
||||
"panelConfig": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen"
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
"decimal": "",
|
||||
"emptyTable": "Keine Daten in der Tabelle verfügbar",
|
||||
"info": "Anzeige von _START_ to _END_ of _TOTAL_ Einträge",
|
||||
"infoEmpty": "Zeigt 0 bis 0 von 0 Einträge",
|
||||
"infoFiltered": "(gefiltert von _MAX_ maximalen Einträgen)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Zeige _MENU_ Einträge",
|
||||
"loadingRecords": "Laden...",
|
||||
"processing": "Verarbeiten...",
|
||||
"search": "Suchen:",
|
||||
"zeroRecords": "Keine passenden Einträge gefunden",
|
||||
"paginate": {
|
||||
"first": "Erster",
|
||||
"last": "Letzter",
|
||||
"next": "Nächste",
|
||||
"previous": "Vorheriger"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": aktivieren, um die Spalte aufsteigend zu sortieren",
|
||||
"sortDescending": ": aktivieren, um die Spalte absteigend zu sortieren"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "Sammlung <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
|
||||
"colvis": "Sichtbarkeit der Spalten",
|
||||
"colvisRestore": "Sichtbarkeit wiederherstellen",
|
||||
"copy": "Kopieren",
|
||||
"copyKeys": "Drücken Sie strg oder u2318 + C, um die Tabellendaten in die Zwischenablage Ihres Systems zu kopieren.<br><br>Zum abbrechen, klicken Sie auf diese Meldung oder drücken Sie Escape.",
|
||||
"copySuccess": {
|
||||
"1": "1 Zeile in die Zwischenablage kopiert",
|
||||
"_": "%d Zeilen in die Zwischenablage kopiert"
|
||||
},
|
||||
"copyTitle": "In die Zwischenablage kopieren",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Alle Zeilen anzeigen",
|
||||
"1": "1 Zeile anzeigen",
|
||||
"_": "%d Zeilen anzeigen"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Drucken"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"0": "Klicken Sie auf eine Zeile, um sie auszuwählen",
|
||||
"1": "%d Zeile ausgewählt",
|
||||
"_": "%d Zeilen ausgewählt"
|
||||
},
|
||||
"cells": {
|
||||
"0": "Klicken Sie auf eine Zelle, um sie auszuwählen",
|
||||
"1": "%d Zelle ausgewählt",
|
||||
"_": "%d Zellen ausgewählt"
|
||||
},
|
||||
"columns": {
|
||||
"0": "Klicken Sie auf eine Spalte, um sie auszuwählen",
|
||||
"1": "%d Spalte ausgewählt",
|
||||
"_": "%d Spalten ausgewählt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"doesNotWorkWithoutJavascript": "<strong>Warnung: </strong>Crafty funktioniert nicht richtig, wenn JavaScript nicht aktiviert ist!"
|
||||
}
|
||||
}
|
@ -12,8 +12,12 @@
|
||||
"embarassing": "Oh my, well, this is embarrassing.",
|
||||
"error": "Error!",
|
||||
"start-error": "Server {} failed to start with error code: {}",
|
||||
"closedPort": "We have detected port {} may not be open on the host network or a firewall is blocking it. Remote client connections to the server may be limited.",
|
||||
"internet": "We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited."
|
||||
"portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.",
|
||||
"internet": "We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.",
|
||||
"eulaTitle": "Agree To EULA",
|
||||
"eulaMsg": "You must agree to the EULA. A copy of the Mojang EULA is linked under this message.",
|
||||
"eulaAgree": "Do you agree?",
|
||||
"noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server."
|
||||
},
|
||||
"404": {
|
||||
"contact": "Contact Crafty Control Support via Discord",
|
||||
@ -41,7 +45,9 @@
|
||||
"serverName": "Server Name",
|
||||
"serverPath": "Server Path",
|
||||
"serverType": "Server Type",
|
||||
"selectType": "Select a Type",
|
||||
"serverVersion": "Server Version",
|
||||
"selectVersion": "Select a Version",
|
||||
"absoluteServerPath": "Absolute path to your server",
|
||||
"serverJar": "Server Jarfile",
|
||||
"minMem": "Minimum Memory",
|
||||
@ -59,7 +65,20 @@
|
||||
"myNewServer": "My New Server",
|
||||
"bePatient": "Please be patient as we ' + (importing ? 'import' : 'download') + ' the server",
|
||||
"importing": "Importing Server...",
|
||||
"downloading": "Downloading Server..."
|
||||
"downloading": "Downloading Server...",
|
||||
"addRole": "Add Server to Existing Role(s)",
|
||||
"autoCreate": "If none are selected Crafty will make one!",
|
||||
"selectRole": "Select Role(s)",
|
||||
"selectZipDir": "Select the directory in the archive you want us to unzip files from",
|
||||
"close": "Close",
|
||||
"save": "Save",
|
||||
"selectRoot": "Select Archive Root Dir",
|
||||
"clickRoot": "Click here to select Root Dir",
|
||||
"explainRoot": "Please click the button below to select your server's root dir inside of the archive"
|
||||
},
|
||||
"usersConfig":{
|
||||
"deleteUser": "Delete user: ",
|
||||
"confirmDelete": "Are you sure you want to delete this user? This action is irreversible."
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Dashboard",
|
||||
@ -124,7 +143,8 @@
|
||||
"description": "Description",
|
||||
"errorCalculatingUptime": "Error Calculating Uptime",
|
||||
"serverTime": "UTC Time",
|
||||
"unableToConnect": "Unable To Connect"
|
||||
"unableToConnect": "Unable To Connect",
|
||||
"serverTimeZone": "Server Time Zone"
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "Server Details",
|
||||
@ -137,7 +157,7 @@
|
||||
"playerControls": "Player Management"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "Stop Auto Scrollling",
|
||||
"stopScroll": "Stop Auto Scrolling",
|
||||
"commandInput": "Enter your command",
|
||||
"sendCommand": "Send command",
|
||||
"start": "Start",
|
||||
@ -152,6 +172,15 @@
|
||||
"bannedPlayers": "Banned Players",
|
||||
"loadingBannedPlayers": "Loading Banned Players"
|
||||
},
|
||||
"serverSchedules":{
|
||||
"areYouSure": "Deleted Scheduled Task?",
|
||||
"confirmDelete": "Do you want to delete this scheduled task? This cannot be undone.",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"cannotSee": "Not seeing everything?",
|
||||
"cannotSeeOnMobile": "Try clicking on a scheduled task for full details."
|
||||
},
|
||||
|
||||
"serverBackups": {
|
||||
"backupNow": "Backup Now!",
|
||||
"backupAtMidnight": "Auto-backup at midnight?",
|
||||
@ -169,7 +198,11 @@
|
||||
"backupTask": "A backup task has been started.",
|
||||
"destroyBackup": "Destroy backup \" + file_to_del + \"?",
|
||||
"confirmDelete": "Do you want to delete this backup? This cannot be undone.",
|
||||
"confirm": "Confirm"
|
||||
"confirm": "Confirm",
|
||||
"options": "Options",
|
||||
"restoring": "Restoring Backup. This may take a while. Please be patient.",
|
||||
"restore": "Restore",
|
||||
"confirmRestore": "Are you sure you want to restore from this backup. All current server files will changed to backup state and will be unrecoverable."
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "The file manager does not work without JavaScript",
|
||||
@ -199,7 +232,8 @@
|
||||
"waitUpload": "Please wait while we upload your files... This may take a while.",
|
||||
"stayHere": "DO NOT LEAVE THIS PAGE!",
|
||||
"close": "Close",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"loadingRecords": "Loading Files..."
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "Server Name",
|
||||
@ -238,7 +272,7 @@
|
||||
"yesDelete": "Yes, delete",
|
||||
"noDelete": "No, go back",
|
||||
"deleteFilesQuestion": "Delete server files from machine?",
|
||||
"deleteFilesQuestionMessage": "Would you like Crafty to delete all server files from the host machine?",
|
||||
"deleteFilesQuestionMessage": "Would you like Crafty to delete all server files from the host machine? <br><br><strong>This includes server backups.</strong>",
|
||||
"yesDeleteFiles": "Yes, delete files",
|
||||
"noDeleteFiles": "No, just remove from panel",
|
||||
"sendingDelete": "Deleting Server",
|
||||
@ -265,7 +299,10 @@
|
||||
"panelConfig": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete"
|
||||
"delete": "Delete",
|
||||
"superConfirmTitle": "Enable Super User? Are you sure?",
|
||||
"superConfirm": "Proceed only if you want this user to have access to EVERYTHING (all user accounts, servers, panel configs, etc). They can even remove your super user access."
|
||||
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
|
350
app/translations/es_ES.json
Normal file
@ -0,0 +1,350 @@
|
||||
{
|
||||
"login": {
|
||||
"forgotPassword": "Olvidé mi contraseña",
|
||||
"login": "Iniciar Sesión",
|
||||
"password": "Contraseña",
|
||||
"username": "Usuario"
|
||||
},
|
||||
"error": {
|
||||
"hereIsTheError": "Aquí está el error.",
|
||||
"contact": "Contacta el soporte de Crafty Control desde Discord",
|
||||
"terribleFailure": "¡Un terrible error!",
|
||||
"embarassing": "Oh por, bueno, esto es vergonzoso.",
|
||||
"error": "Error!",
|
||||
"start-error": "Servidor {} fallo al iniciar con código de error: {}",
|
||||
"closedPort": "Hemos detectado que el puerto {} podría no estar abierto en la red del host o un firewall lo está bloqueando. Las conexiones remotas de clientes al servidor podrían estar limitadas.",
|
||||
"internet": "Hemos detectado que la maquina ejecutando Crafty no tiene acceso a internet. Las conexiones de clientes al servidor podrían estar limitadas.",
|
||||
"eulaTitle": "Aceptar EULA",
|
||||
"eulaMsg": "Debes aceptar el EULA. Una copia del EULA de Mojang esta vinculada debajo de este mensaje.",
|
||||
"eulaAgree": "Estás de acuerdo?",
|
||||
"noJava": "Server {} fallo al iniciar con código de error: Detectamos que Java no esta instalado, Por favor instale java y inicie el servidor."
|
||||
},
|
||||
"404": {
|
||||
"contact": "Contacta el soporte de Crafty Control desde Discord",
|
||||
"unableToFind": "No pudimos encontrar la página que buscabas. Vuelve a intentarlo o regresa y refresca la página.",
|
||||
"notFound": "Página no encontrada"
|
||||
},
|
||||
"footer": {
|
||||
"version": "Versión",
|
||||
"copyright": "Copyright",
|
||||
"allRightsReserved": "Todos los derechos reservados"
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "Panel de control",
|
||||
"servers": "Servers",
|
||||
"documentation": "Documentación",
|
||||
"credits": "Creditos",
|
||||
"contribute": "Contribuir",
|
||||
"newServer": "Crear nuevo Servidor",
|
||||
"navigation": "Navegación"
|
||||
},
|
||||
"serverWizard": {
|
||||
"newServer": "Crear Servidor",
|
||||
"importServer": "Importar Servidor existente",
|
||||
"importZip": "Importar desde archivo Zip",
|
||||
"serverName": "Nombre del servidor",
|
||||
"serverPath": "Dirección del servidor",
|
||||
"serverType": "Tipo de servidor",
|
||||
"selectType": "Selecciona un tipo",
|
||||
"serverVersion": "Versión del servidor",
|
||||
"selectVersion": "Selecciona una versión",
|
||||
"absoluteServerPath": "Dirección absoluta del servidor",
|
||||
"serverJar": "Archivo Jar del servidor",
|
||||
"minMem": "Memoria mínima",
|
||||
"maxMem": "Memoria máxima",
|
||||
"serverPort": "Puerto del servidor",
|
||||
"defaultPort": "25565 (Por defecto)",
|
||||
"sizeInGB": "Tamaño en GB",
|
||||
"zipPath": "Dirección del servidor",
|
||||
"absoluteZipPath": "Dirección absoluta del servidor",
|
||||
"resetForm": "Limpiar formulario",
|
||||
"importServerButton": "Importar Servidor!",
|
||||
"buildServer": "Construir Servidor!",
|
||||
"quickSettings": "Ajustes rápidos",
|
||||
"quickSettingsDescription": "No te preocupes, puedes cambiarlos más tarde.",
|
||||
"myNewServer": "Mi nuevo Servidor",
|
||||
"bePatient": "Por favor tenga paciencia, ya que estamos ' + (importing ? 'import' : 'download') + ' el servidor.",
|
||||
"importing": "Importando Servidor...",
|
||||
"downloading": "Descargando Servidor...",
|
||||
"addRole": "Añadir Servidor a grupos existentes..",
|
||||
"autoCreate": "Si no se selecciona ninguno, ¡Crafty creara uno!",
|
||||
"selectRole": "Seleccionar Grupo(s)"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Panel de control",
|
||||
"memUsage": "Uso de memoria",
|
||||
"cpuUsage": "Uso de CPU",
|
||||
"host": "Host",
|
||||
"players": "Jugadores",
|
||||
"backups": "Backups",
|
||||
"newServer": "Crear nuevo Servidor",
|
||||
"allServers": "Todos los servidores",
|
||||
"server": "Server",
|
||||
"actions": "Acciones",
|
||||
"world": "Mundo",
|
||||
"motd": "MOTD",
|
||||
"version": "Versión",
|
||||
"status": "Estado",
|
||||
"online": "En línea",
|
||||
"offline": "Desconectado",
|
||||
"lastBackup": "Último:",
|
||||
"nextBackup": "Próximo:",
|
||||
"servers": "Servers",
|
||||
"cannotSeeOnMobile": "¿No puedes verlo todo en móvil?",
|
||||
"cannotSee": "¿No puedes verlo todo?",
|
||||
"cannotSeeOnMobile2": "Intenta desplazar la tabla desde los lados..",
|
||||
"max": "Max",
|
||||
"avg": "Avg",
|
||||
"bePatientStart": "Espere mientras iniciamos el servidor.<br /> Esta pantalla se actualizará en un momento",
|
||||
"bePatientStop": "Espere mientras detenemos el servidor.<br /> Esta pantalla se actualizará en un momento",
|
||||
"bePatientRestart": "Espere mientras reiniciamos el servidor.<br /> Esta pantalla se actualizará en un momento",
|
||||
"bePatientClone": "Espere mientras clonamos el servidor.<br /> Esta pantalla se actualizará en un momento",
|
||||
"sendingCommand": "Enviando tu comando",
|
||||
"cpuCurFreq": "Reloj de CPU Actual",
|
||||
"cpuMaxFreq": "Reloj de CPU Maximo",
|
||||
"cpuCores": "Nucleos de CPU",
|
||||
"start": "Iniciar",
|
||||
"stop": "Detener",
|
||||
"clone": "Clonar",
|
||||
"kill": "Matar Proceso",
|
||||
"restart": "Reiniciar",
|
||||
"killing": "Matando el proceso...",
|
||||
"starting": "Inicio-retrasado",
|
||||
"delay-explained": "El agente/servicio ha iniciado recientemente y está retrasando el inicio de la instancia del servidor de Minecraft.",
|
||||
"no-servers": "Actualmente no hay servidores. Para comenzar, haga click",
|
||||
"welcome": "Bienvenido a Crafty Controller"
|
||||
},
|
||||
"accessDenied": {
|
||||
"accessDenied": "Acceso Denegado",
|
||||
"noAccess": "No tienes acceso a este recurso",
|
||||
"contactAdmin": "Contacta con el administrador de tu servidor para acceder a este recurso, o si crees que deberías tener acceso a este recurso, comuníquese con el soporte.",
|
||||
"contact": "Contacta el soporte de Crafty Control desde Discord"
|
||||
},
|
||||
"serverStats": {
|
||||
"online": "En línea",
|
||||
"offline": "Desconectado",
|
||||
"serverStatus": "Estado del Servidor",
|
||||
"serverStarted": "Servidor Iniciado",
|
||||
"serverUptime": "Actividad del Servidor",
|
||||
"players": "Jugadores",
|
||||
"memUsage": "Uso de memoria",
|
||||
"cpuUsage": "Uso de CPU",
|
||||
"version": "Versión",
|
||||
"description": "Descripción",
|
||||
"errorCalculatingUptime": "Error calculando tiempo de actividad",
|
||||
"serverTime": "Hora UTC",
|
||||
"unableToConnect": "No se pudo conectar"
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "Detalles del Servidor",
|
||||
"terminal": "Terminal",
|
||||
"logs": "Registros",
|
||||
"schedule": "Programación Tareas",
|
||||
"backup": "Backup",
|
||||
"files": "Archivos",
|
||||
"config": "Configuración",
|
||||
"playerControls": "Gestionar jugadores"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "Detener Scrolling automatico",
|
||||
"commandInput": "Introducir tu comando",
|
||||
"sendCommand": "Enviar comando",
|
||||
"start": "Iniciar",
|
||||
"restart": "Reiniciar",
|
||||
"stop": "Detener",
|
||||
"updating": "Actualizando...",
|
||||
"starting": "Inicio-retrasado",
|
||||
"delay-explained": "El agente/servicio ha recientemente iniciado y está retrasando el inicio de la instancia del servidor de Minecraft."
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "Jugadores",
|
||||
"bannedPlayers": "Jugadores Baneados",
|
||||
"loadingBannedPlayers": "Cargando jugadores baneados"
|
||||
},
|
||||
"serverBackups": {
|
||||
"backupNow": "¡Haga una copia de seguridad ahora!",
|
||||
"backupAtMidnight": "¿Copia de seguridad automática a medianoche?",
|
||||
"storageLocation": "Ubicación de almacenamiento",
|
||||
"storageLocationDesc": "¿Dónde quieres almacenar las copias de seguridad?",
|
||||
"maxBackups": "Máxima cantidad de Copias de seguridad",
|
||||
"maxBackupsDesc": "Crafty no almacenará más de N copias de seguridad, eliminando la más antigua (Ingrese 0 para mantenerlas todas)",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"currentBackups": "Copias de seguridad actuales",
|
||||
"download": "Descargar",
|
||||
"path": "Dirección",
|
||||
"size": "Tamaño",
|
||||
"delete": "Eliminar",
|
||||
"backupTask": "Se ha iniciado una tarea de copia de seguridad.",
|
||||
"destroyBackup": "¿Destruir copia de seguridad \" + file_to_del + \"?",
|
||||
"confirmDelete": "¿Quieres eliminar esta copia de seguridad? Esto no se puede deshacer.",
|
||||
"confirm": "Confirmar",
|
||||
"options": "Opciones",
|
||||
"restoring": "Restaurando copia de seguridad. Esto puede tomar un tiempo. Sea paciente.",
|
||||
"restore": "Restaurar",
|
||||
"confirmRestore": "Esta seguro de que quiere restaurar desde este backup. Todos los archivos actuales del servidor seran cambiados al estado del backup y seran irrecuperables."
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "El administrador de archivos no funciona sin JavaScript ",
|
||||
"error": "Error al encontrar archivos",
|
||||
"files": "Archivos",
|
||||
"default": "Predeterminado",
|
||||
"save": "Guardar",
|
||||
"editingFile": "Editar archivo",
|
||||
"delete": "Eliminar",
|
||||
"createFile": "Crear archivo",
|
||||
"createDir": "Crear directorio",
|
||||
"rename": "Renombrar",
|
||||
"createFileQuestion": "Que nombre quiere darle al nuevo archivo?",
|
||||
"createDirQuestion": "Que nombre quiere darle al nuevo directorio?",
|
||||
"renameItemQuestion": "Cuál debería ser el nuevo nombre?",
|
||||
"deleteItemQuestion": "¿Estás seguro de que quieres eliminar \" + name + \"?",
|
||||
"deleteItemQuestionMessage": "¡Estas eliminando \\\"\" + path + \"\\\"!<br/><br/>¡Esta acción será irreversible y se perderá para siempre!",
|
||||
"yesDelete": "Si, entiendo las consecuencias",
|
||||
"noDelete": "No",
|
||||
"unsupportedLanguage": "Advertencia: este no es un tipo de archivo admitido",
|
||||
"keybindings": "Atajos de teclado",
|
||||
"fileReadError": "Error de lectura del archivo",
|
||||
"upload": "Subir",
|
||||
"unzip": "Descomprimir (UnZip)",
|
||||
"clickUpload": "Click aquí para seleccionar tus archivos",
|
||||
"uploadTitle": "Subir archivos a: ",
|
||||
"waitUpload": "Espera mientras subimos tus archivos... Esto puede tomar un tiempo.",
|
||||
"stayHere": "NO SALGA DE ESTA PÁGINA",
|
||||
"close": "Cerrar",
|
||||
"download": "Descargar"
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "Nombre del servidor",
|
||||
"serverNameDesc": "¿Cómo quieres nombrar el servidor?",
|
||||
"serverPath": "Directorio de trabajo del servidor",
|
||||
"serverPathDesc": "Dirección absoluta del servidor (sin incluir el ejecutable)",
|
||||
"serverLogLocation": "Ubicación del registro del servidor",
|
||||
"serverLogLocationDesc": "Dirección absoluta al archivo de registro",
|
||||
"serverExecutable": "Ejecutable del servidor",
|
||||
"serverExecutableDesc": "El archivo ejecutable del servidor.",
|
||||
"serverExecutionCommand": "Comando de ejecución del Servidor",
|
||||
"serverExecutionCommandDesc": "Se ejecutara en una terminal oculta para iniciar el servidor.",
|
||||
"serverStopCommand": "Comando de detención del Servidor",
|
||||
"serverStopCommandDesc": "Comando que enviar al programa para detener el servidor.",
|
||||
"serverAutostartDelay": "Retraso del inicio automático del servidor.",
|
||||
"serverAutostartDelayDesc": "Tiempo de retraso antes del inicio automático (si está habilitado debajo)",
|
||||
"serverIP": "Server IP",
|
||||
"serverIPDesc": "IP a la que Crafty debería conectarse para obtener estadísticas (pruebe con una ip real en lugar de 127.0.0.1 si tienes problemas)",
|
||||
"serverPort": "Puerto del Servidor",
|
||||
"serverPortDesc": "Puerto al que Crafty debería conectarse para obtener estadísticas",
|
||||
"removeOldLogsAfter": "Eliminar registros antiguos después de",
|
||||
"removeOldLogsAfterDesc": "¿Cuántos días debe ser un archivo de registro para ser eliminado (0 es desactivado)?",
|
||||
"serverAutoStart": "Inicio automático del servidor",
|
||||
"serverCrashDetection": "Detección de crasheos del Servidor",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"deleteServer": "Eliminar Servidor",
|
||||
"stopBeforeDeleting": "Detenga el servidor antes de eliminarlo.",
|
||||
"exeUpdateURLDesc": "URL de descarga directa para actualizaciones.",
|
||||
"exeUpdateURL": "URL de actualización para el ejecutable del Servidor",
|
||||
"update": "Actualizar Ejecutable",
|
||||
"bePatientUpdate": "Tenga paciencia mientras actualizamos el servidor. El tiempo de descarga puede variar según la velocidad del Internet...<br /> Esta pantalla se actualizará en unos momentos.",
|
||||
"sendingRequest": "Enviando tu solicitud...",
|
||||
"deleteServerQuestion": "¿Eliminar Servidor?",
|
||||
"deleteServerQuestionMessage": "¿Estás seguro de que desea eliminar este servidor? Después de esto no hay vuelta atrás...",
|
||||
"yesDelete": "Si, borralo.",
|
||||
"noDelete": "No, vuelve atrás.",
|
||||
"deleteFilesQuestion": "¿Eliminar archivos del servidor de la máquina?",
|
||||
"deleteFilesQuestionMessage": "¿Le gustaría que Crafty elimine todos los archivos del servidor de la máquina host? <br><br><strong>Esto incluye backups del servidor.</strong>",
|
||||
"yesDeleteFiles": "Si, borra los archivos.",
|
||||
"noDeleteFiles": "No, solo remover del panel.",
|
||||
"sendingDelete": "Eliminando servidor",
|
||||
"bePatientDelete": "Tenga paciencia mientras eliminamos su servidor del panel de Crafty. Esta pantalla se cerrará en unos momentos.",
|
||||
"bePatientDeleteFiles" : "Tenga paciencia mientras eliminamos su servidor del panel de Crafty y eliminamos todos los archivos. Esta pantalla se cerrará en unos momentos."
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Server Config Area",
|
||||
"desc": "Here is where you can change the configuration of your server",
|
||||
"perms": [
|
||||
"It is recommended to <code>NOT</code> change the paths of a server managed by Crafty.",
|
||||
"Changing paths <code>CAN</code> break things, especially on Linux type operating systems where file permissions are more locked down.",
|
||||
"<br /><br/>",
|
||||
"If you feel you have to change a where a server is located you may do so as long as you give the \"crafty\" user permission to read / write to the server path.",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"On Linux this is best done by executing the following:<br />",
|
||||
"<code>",
|
||||
" sudo chown crafty:crafty /path/to/your/server -R<br />",
|
||||
" sudo chmod 2775 /path/to/your/server -R<br />",
|
||||
"</code>"
|
||||
]
|
||||
},
|
||||
"panelConfig": {
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
"decimal": "",
|
||||
"emptyTable": "No hay datos disponibles en la tabla",
|
||||
"info": "Mostrando _START_ a _END_ de _TOTAL_ entradas",
|
||||
"infoEmpty": "Mostrando 0 a 0 de 0 entradas",
|
||||
"infoFiltered": "(filtrado de _MAX_ entradas totales)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Mostrar _MENU_ entradas",
|
||||
"loadingRecords": "Cargando...",
|
||||
"processing": "Procesando...",
|
||||
"search": "Buscar:",
|
||||
"zeroRecords": "No se encontraron registros coincidentes",
|
||||
"paginate": {
|
||||
"first": "Primero",
|
||||
"last": "Ultimo",
|
||||
"next": "Siguiente",
|
||||
"previous": "Anterior"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": activar para ordenar las columnas de masnera ascendente",
|
||||
"sortDescending": ": activar para ordenar las columnas de masnera descendente"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "Colección <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
|
||||
"colvis": "Visibilidad de las columnas",
|
||||
"colvisRestore": "Restaurar visibilidad",
|
||||
"copy": "Copiar",
|
||||
"copyKeys": "Presiona ctrl o u2318 + C para copiar los datos de la tabla al portapapeles de tu sistema.<br><br>Para cancelar, click en este mensaje o presiona escape.",
|
||||
"copySuccess": {
|
||||
"1": "Copiado 1 fila al portapapeles",
|
||||
"_": "Copiadas %d filas al portapapeles"
|
||||
},
|
||||
"copyTitle": "Copiar al Portapapeles",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Mostrar todas las filas",
|
||||
"1": "Mostrar 1 fila",
|
||||
"_": "Mostrar %d filas"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Imprimir"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"0": "Click en una fila para seleccionarla",
|
||||
"1": "%d fila seleccionada",
|
||||
"_": "%d filas seleccionadas"
|
||||
},
|
||||
"cells": {
|
||||
"0": "Click en una celda para seleccionarla",
|
||||
"1": "%d celda seleccionada",
|
||||
"_": "%d celdas seleccionadas"
|
||||
},
|
||||
"columns": {
|
||||
"0": "Click en una columna para seleccionarla",
|
||||
"1": "%d columna seleccionada",
|
||||
"_": "%d columnas seleccionadas"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"doesNotWorkWithoutJavascript": "<strong>Aviso: </strong>¡Crafty no funciona correctamente cuando JavaScript no está habilitado!"
|
||||
}
|
||||
}
|
@ -13,7 +13,11 @@
|
||||
"error": "Virhe!",
|
||||
"start-error": "Palvelin {} ei käynnistynyt virhekoodilla: {}",
|
||||
"closedPort": "Olemme havainneet, että portti {} ei ehkä ole auki isäntäverkossa tai palomuuri estää sen. Etäasiakkaan yhteydet palvelimeen voivat olla rajallisia.",
|
||||
"internet": "Olemme havainneet, että Crafty -koneella ei ole Internet -yhteyttä. Asiakasyhteydet palvelimelle voivat olla rajalliset."
|
||||
"internet": "Olemme havainneet, että Crafty -koneella ei ole Internet -yhteyttä. Asiakasyhteydet palvelimelle voivat olla rajalliset.",
|
||||
"eulaTitle": "Hyväksy EULA",
|
||||
"eulaMsg": "Sinun on hyväksyttävä EULA. Kopio Mojang EULA:sta on linkitetty tämän viestin alla.",
|
||||
"eulaAgree": "Oletko samaa mieltä?",
|
||||
"noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server."
|
||||
},
|
||||
"404": {
|
||||
"contact": "Ota yhteyttä Crafty Control -tukeen Discordin kautta",
|
||||
@ -41,7 +45,9 @@
|
||||
"serverName": "Palvelimen nimi",
|
||||
"serverPath": "Palvelimen polku",
|
||||
"serverType": "Palvelimen tyypi",
|
||||
"selectType": "Valitse tyypi",
|
||||
"serverVersion": "Palvelimen versio",
|
||||
"selectVersion": "Valitse versio",
|
||||
"absoluteServerPath": "Absoluuttinen polku palvelimeesi",
|
||||
"serverJar": "Palvelimen Jar -tiedosto",
|
||||
"minMem": "Minimi Muisti",
|
||||
@ -59,7 +65,11 @@
|
||||
"myNewServer": "Minun uusi palvelin",
|
||||
"bePatient": "Ole kärsivällinen, kun ' + (importing ? 'tuomme' : 'lataamme') + ' palvelinta",
|
||||
"importing": "Tuomme palvelinta...",
|
||||
"downloading": "Lataamme palvelinta..."
|
||||
"downloading": "Lataamme palvelinta...",
|
||||
"addRole": "Lisää Palvelin Olemassa Oleviin Rooleihin",
|
||||
"autoCreate": "Jos ketään ei valita, Crafty tekee sellaisen!",
|
||||
"selectRole": "Valitse roolit"
|
||||
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Kojelauta",
|
||||
@ -169,7 +179,11 @@
|
||||
"backupTask": "Varmuuskopiointitehtävä on aloitettu.",
|
||||
"destroyBackup": "Tuhotaanko varmuuskopio \" + file_to_del + \"?",
|
||||
"confirmDelete": "Haluatko poistaa tämän varmuuskopion? Tätä ei voi peruuttaa.",
|
||||
"confirm": "Vahvista"
|
||||
"confirm": "Vahvista",
|
||||
"options": "Vaihtoehtoja",
|
||||
"restoring": "Varmuuskopion palauttaminen. Tämä voi kestää hetken. Olkaa kärsivällisiä.",
|
||||
"restore": "Palauttaa",
|
||||
"confirmRestore": "Haluatko varmasti palauttaa tämän varmuuskopion. Kaikki nykyiset palvelintiedostot muutetaan varmuuskopiotilaan, eikä niitä voida palauttaa."
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "Tiedostojenhallinta ei toimi ilman JavaScriptiä",
|
||||
@ -227,7 +241,23 @@
|
||||
"save": "Tallenna",
|
||||
"cancel": "Peruuta",
|
||||
"deleteServer": "Poista palvelin",
|
||||
"stopBeforeDeleting": "Pysäytä palvelin ennen sen poistamista"
|
||||
"stopBeforeDeleting": "Pysäytä palvelin ennen sen poistamista",
|
||||
"exeUpdateURLDesc": "Direct Download URL for updates.",
|
||||
"exeUpdateURL": "Palvelimen suoritettavan päivityksen URL-osoite",
|
||||
"update": "Päivitä suoritettava",
|
||||
"bePatientUpdate": "Ole kärsivällinen, kun päivitämme palvelinta. Latausajat voivat vaihdella Internet-nopeutesi mukaan.<br /> Tämä näyttö päivittyy hetkessä",
|
||||
"sendingRequest": "Pyyntöäsi lähetetään...",
|
||||
"deleteServerQuestion": "Poistetaanko palvelin?",
|
||||
"deleteServerQuestionMessage": "Haluatko varmasti poistaa tämän palvelimen? Tämän jälkeen ei ole paluuta...",
|
||||
"yesDelete": "Kyllä, poista",
|
||||
"noDelete": "Ei, mene takaisin",
|
||||
"deleteFilesQuestion": "Poistetaanko palvelintiedostot koneelta?",
|
||||
"deleteFilesQuestionMessage": "Haluatko Craftyn poistavan kaikki palvelintiedostot isäntäkoneelta? <br><br><strong> Tämä sisältää palvelimen varmuuskopiot. <strong>",
|
||||
"yesDeleteFiles": "Kyllä, poista tiedostoja",
|
||||
"noDeleteFiles": "Ei, poista vain paneelista",
|
||||
"sendingDelete": "Poistetaan palvelinta",
|
||||
"bePatientDelete": "Ole kärsivällinen, kun poistamme palvelimesi Crafty-paneelista. Tämä näyttö sulkeutuu hetken kuluttua.",
|
||||
"bePatientDeleteFiles" : "Ole kärsivällinen, kun poistamme palvelimesi Crafty-paneelista ja poistamme kaikki tiedostot. Tämä näyttö sulkeutuu hetken kuluttua."
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Palvelimen asetukset",
|
||||
|
@ -13,7 +13,11 @@
|
||||
"error": "Erreur !",
|
||||
"start-error": "Le serveur {} n'a pas pu démarrer avec le code d'erreur : {}",
|
||||
"closedPort": "Nous avons détecté que le port {} n'est peut-être pas ouvert sur le réseau hôte ou qu'un pare-feu le bloque. Les connexions des clients distants au serveur peuvent être limitées.",
|
||||
"internet": "Nous avons détecté que la machine exécutant Crafty n'a pas de connexion à Internet. Les connexions client au serveur peuvent être limitées."
|
||||
"internet": "Nous avons détecté que la machine exécutant Crafty n'a pas de connexion à Internet. Les connexions client au serveur peuvent être limitées.",
|
||||
"eulaTitle": "Accepter le EULA",
|
||||
"eulaMsg": "Vous devez accepter le EULA. Une copie du CLUF de Mojang est liée sous ce message.",
|
||||
"eulaAgree": "Êtes-vous d'accord?",
|
||||
"noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server."
|
||||
},
|
||||
"404": {
|
||||
"contact": "Contacter le Support de Crafty Control via Discord",
|
||||
@ -41,7 +45,9 @@
|
||||
"serverName": "Non du Serveur",
|
||||
"serverPath": "Chemin du Serveur",
|
||||
"serverType": "Type du Serveur",
|
||||
"selectType": "Selectionner un Type",
|
||||
"serverVersion": "Version du Serveur",
|
||||
"selectVersion": "Selectionner une Version",
|
||||
"absoluteServerPath": "Chemin absolu de Votre Serveur",
|
||||
"serverJar": "Fichier Jar du Serveur",
|
||||
"minMem": "Mémoire Minimum",
|
||||
@ -59,7 +65,10 @@
|
||||
"myNewServer": "Mon Nouveau Serveur",
|
||||
"bePatient": "Merci de patienter pendant que nous ' + (importing ? 'importons' : 'téléchargeons') + ' le serveur",
|
||||
"importing": "Importation du Serveur ...",
|
||||
"downloading": "Téléchargement du Serveur ..."
|
||||
"downloading": "Téléchargement du Serveur ...",
|
||||
"addRole": "Ajouter le server au(x) rôle(s) existant(s)",
|
||||
"autoCreate": "Si aucun n'est sélectionné, Crafty en créera un !",
|
||||
"selectRole": "Sélectionnez le rôle(s)"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Tableau de Bord",
|
||||
@ -169,7 +178,11 @@
|
||||
"backupTask": "Une sauvegarde vient de démarrer.",
|
||||
"destroyBackup": "Supprimer la sauvegarde \" + file_to_del + \" ?",
|
||||
"confirmDelete": "Es-tu sûr de vouloir supprimer cette sauvegarde ? Tu ne pourras pas revenir en arrière.",
|
||||
"confirm": "Confirmer"
|
||||
"confirm": "Confirmer",
|
||||
"options": "Options",
|
||||
"restoring": "Restauration de la sauvegarde. Cela peut prendre un peu de temps. S'il vous plaît soyez patient.",
|
||||
"restore": "Restaurer",
|
||||
"restoreConfirm": "Êtes-vous sûr de vouloir restaurer à partir de cette sauvegarde. Tous les fichiers du serveur actuel passeront à l'état de sauvegarde et seront irrécupérables."
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "Le gestionnaire de fichiers ne fonctionne pas sans JavaScript",
|
||||
@ -238,7 +251,7 @@
|
||||
"yesDelete": "Oui, Supprimer",
|
||||
"noDelete": "Non, revenir en arrière",
|
||||
"deleteFilesQuestion": "Supprimer les fichiers de la machine ?",
|
||||
"deleteFilesQuestionMessage": "Veux-tu que Crafty supprime tous les fichiers du serveur de la machine hôte ?",
|
||||
"deleteFilesQuestionMessage": "Veux-tu que Crafty supprime tous les fichiers du serveur de la machine hôte? <br><br><strong>Cela inclut les sauvegardes du serveur.</strong>",
|
||||
"yesDeleteFiles": "Oui, Supprimer les fichier",
|
||||
"noDeleteFiles": "Non, Supprimer uniquement du tabelau de bord",
|
||||
"sendingDelete": "Suppression du Serveur",
|
||||
|
346
app/translations/hr_HR.json
Normal file
@ -0,0 +1,346 @@
|
||||
{
|
||||
"login": {
|
||||
"forgotPassword": "Zaboravio si lozinku?",
|
||||
"login": "Prijava",
|
||||
"password": "Lozinka",
|
||||
"username": "Korisničko ime"
|
||||
},
|
||||
"error": {
|
||||
"hereIsTheError": "Ovdje je pogreška",
|
||||
"contact": "Kontaktirajte Crafty Control podršku putem Discorda",
|
||||
"terribleFailure": "Kakav užasan neuspjeh!",
|
||||
"embarassing": "Uhh, ovo je sramotno.",
|
||||
"error": "Greška!",
|
||||
"start-error": "Poslužitelj {} nije usješno pokrenut s kodom pogreške: {}",
|
||||
"closedPort": "Otkrili smo da port {} nije otvoren na glavnoj mreži ili ga vatrozid blokira. Povezivanje udaljenog klijenta s poslužiteljem može biti ograničeno.",
|
||||
"internet": "Otkrili smo da uređaj koji pokreće Crafty nije povezan s internetom. Povezivanje klijenta s poslužiteljem može biti ograničeno.",
|
||||
"eulaTitle": "Prihvatite EULA-u",
|
||||
"eulaMsg": "Morate prihvatiti EULA-u. Poveznica Mojangove EULA-e se nalazi ispod ove poruke.",
|
||||
"eulaAgree": "Slažete li se?"
|
||||
},
|
||||
"404": {
|
||||
"contact": "Kontaktirajte Crafty Control podršku putem Discorda",
|
||||
"unableToFind": "Nismo u mogućnosti pronaći stranicu koju tražite. Molimo pokušajte kasnije, ili se vratite nazad i osvježite stranicu.",
|
||||
"notFound": "Stranica nije pronađena"
|
||||
},
|
||||
"footer": {
|
||||
"version": "Verzija",
|
||||
"copyright": "Autorska prava",
|
||||
"allRightsReserved": "Sva prava zadržana"
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "Upravljačka ploča",
|
||||
"servers": "Poslužitelji",
|
||||
"documentation": "Dokumentacija",
|
||||
"credits": "Zasluge",
|
||||
"contribute": "Doprinesite",
|
||||
"newServer": "Stvorite novi poslužitelj",
|
||||
"navigation": "Navigacija"
|
||||
},
|
||||
"serverWizard": {
|
||||
"newServer": "Stvorite novi poslužitelj",
|
||||
"importServer": "Uvezite postojeći poslužitelj",
|
||||
"importZip": "Uvoz iz Zip datoteke",
|
||||
"serverName": "Ime poslužitelja",
|
||||
"serverPath": "Mjesto poslužitelja",
|
||||
"serverType": "Tip poslužitelja",
|
||||
"selectType": "Odaberite vrstu",
|
||||
"serverVersion": "Verzija poslužitelja",
|
||||
"selectVersion": "Odaberite verziju",
|
||||
"absoluteServerPath": "Apsolutna putanja vašeg poslužitelja",
|
||||
"serverJar": "Jar datoteka poslužitelja",
|
||||
"minMem": "Minimalna memorija",
|
||||
"maxMem": "Maksimalna memorija",
|
||||
"serverPort": "Port poslužitelja",
|
||||
"defaultPort": "25565 zadano",
|
||||
"sizeInGB": "Veličina u GB",
|
||||
"zipPath": "Mjesto poslužitelja",
|
||||
"absoluteZipPath": "Apsolutna putanja vašeg poslužitelja",
|
||||
"resetForm": "Poništi obrazac",
|
||||
"importServerButton": "Uvezite poslužitelj!",
|
||||
"buildServer": "Izgradite poslužitelj!",
|
||||
"quickSettings": "Brze postavke",
|
||||
"quickSettingsDescription": "Bez brige, ovo možete promijeniti kasnije",
|
||||
"myNewServer": "Moj novi poslužitelj",
|
||||
"bePatient": "Molim vas strpite se dok ' + (importing ? 'uvozimo' : 'preuzimamo') + ' poslužitelj",
|
||||
"importing": "Uvoz poslužitelja...",
|
||||
"downloading": "Preuzimanje poslužitelja...",
|
||||
"addRole": "Pridružite poslužitelj postojećim ulogama",
|
||||
"autoCreate": "Ako nijedna nije odabrana, Crafty će stvoriti jednu!",
|
||||
"selectRole": "Odaberi ulogu"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Upravljačka ploča",
|
||||
"memUsage": "Upotreba memorije",
|
||||
"cpuUsage": "Upotreba procesora",
|
||||
"host": "Domaćin",
|
||||
"players": "Igrači",
|
||||
"backups": "Sigurnosne kopije",
|
||||
"newServer": "Stvorite novi poslužitelj",
|
||||
"allServers": "Svi poslužitelji",
|
||||
"server": "Poslužitelj",
|
||||
"actions": "Radnje",
|
||||
"world": "Svijet",
|
||||
"motd": "MOTD",
|
||||
"version": "Verzija",
|
||||
"status": "Status",
|
||||
"online": "Na mreži",
|
||||
"offline": "Izvan mreže",
|
||||
"lastBackup": "Posljednji:",
|
||||
"nextBackup": "Sljedeći:",
|
||||
"servers": "Poslužitelji",
|
||||
"cannotSeeOnMobile": "Ne vidite sve na mobitelu?",
|
||||
"cannotSee": "Ne vidite sve?",
|
||||
"cannotSeeOnMobile2": "Pokušajte listati tablicu postrance.",
|
||||
"max": "Maksimalno",
|
||||
"avg": "Prosjek",
|
||||
"bePatientStart": "Molimo budite strpljivi dok pokrećemo poslužitelj.<br />Zaslon će se osvježiti za par trenutaka",
|
||||
"bePatientStop": "Molimo budite strpljivi dok zaustavljamo poslužitelj.<br /> Zaslon će se osvježiti za par trenutaka",
|
||||
"bePatientRestart": "Molimo budite strpljivi dok ponovno pokrećemo poslužitelj.<br /> Zaslon će se osvježiti za par trenutaka",
|
||||
"bePatientClone": "Molimo budite strpljivi dok kloniramo poslužitelj.<br /> Zaslon će se osvježiti za par trenutaka",
|
||||
"sendingCommand": "Slanje vaše naredbe",
|
||||
"cpuCurFreq": "Trenutni takt procesora",
|
||||
"cpuMaxFreq": "Maksimalni takt procesora",
|
||||
"cpuCores": "Jezgre procesora",
|
||||
"start": "Pokreni",
|
||||
"stop": "Zaustavi",
|
||||
"clone": "Kloniraj",
|
||||
"kill": "Zaustavi postupak",
|
||||
"restart": "Ponovno pokreni",
|
||||
"killing": "Zaustavljanje postupka...",
|
||||
"starting": "Odgođeno pokretanje",
|
||||
"delay-explained": "Usluga je nedavno započeta i odgađa pokretanje instance poslužitelja Minecrafta",
|
||||
"no-servers": "Trenutno nema poslužitelja. Da biste započeli, kliknite",
|
||||
"welcome": "Dobrodošli u Crafty Controller"
|
||||
},
|
||||
"accessDenied": {
|
||||
"accessDenied": "Pristup odbijen",
|
||||
"noAccess": "Nemate pristup ovom resursu",
|
||||
"contactAdmin": "Obratite se administratoru poslužitelja kako biste pristupili ovom resursu ili ako mislite da biste trebali imati pristup ovom resursu, kontaktirajte podršku.",
|
||||
"contact": "Kontaktirajte Crafty Control podršku putem Discorda"
|
||||
},
|
||||
"serverStats": {
|
||||
"online": "Na mreži",
|
||||
"offline": "Izvan mreže",
|
||||
"serverStatus": "Status poslužitelja",
|
||||
"serverStarted": "Poslužitelj pokrenut",
|
||||
"serverUptime": "Vrijeme rada poslužitelja",
|
||||
"players": "Igrači",
|
||||
"memUsage": "Upotreba memorije",
|
||||
"cpuUsage": "Upotreba procesora",
|
||||
"version": "Verzija",
|
||||
"description": "Opis",
|
||||
"errorCalculatingUptime": "Pogreška pri izračunavanja vremena neprekidnog rada",
|
||||
"serverTime": "UTC vrijeme",
|
||||
"unableToConnect": "Povezivanje nije moguće"
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "Pojedinosti poslužitelja",
|
||||
"terminal": "Terminal",
|
||||
"logs": "Zapisnik",
|
||||
"schedule": "Raspored",
|
||||
"backup": "Sigurnosna kopija",
|
||||
"files": "Datoteke",
|
||||
"config": "Konfiguracija",
|
||||
"playerControls": "Upravljanje igračima"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "Zaustavi automatsko listanje",
|
||||
"commandInput": "Unesi naredbu",
|
||||
"sendCommand": "Pošalji naredbu",
|
||||
"start": "Pokreni",
|
||||
"restart": "Ponovno pokreni",
|
||||
"stop": "Zaustavi",
|
||||
"updating": "Ažuriranje...",
|
||||
"starting": "Odgođeno pokretanje",
|
||||
"delay-explained": "Usluga je nedavno započeta i odgađa pokretanje instance poslužitelja Minecrafta"
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "Igrači",
|
||||
"bannedPlayers": "Suspendirani igrači",
|
||||
"loadingBannedPlayers": "Učitavanje liste suspendiranih igrača"
|
||||
},
|
||||
"serverBackups": {
|
||||
"backupNow": "Odmah napravi sigurnosnu kopiju!",
|
||||
"backupAtMidnight": "Automatsko sigurnosno kopiranje u ponoć?",
|
||||
"storageLocation": "Mjesto pohrane",
|
||||
"storageLocationDesc": "Gdje želite pohraniti sigurnosne kopije?",
|
||||
"maxBackups": "Maksimalan broj sigurnosnih kopija",
|
||||
"maxBackupsDesc": "Crafty neće pohraniti više od zadanog broja sigurnosnih kopija, brišući najstarije (unesite 0 da biste zadržali sve)",
|
||||
"save": "Spremi",
|
||||
"cancel": "Odustani",
|
||||
"currentBackups": "Trenutne sigurnosne kopije",
|
||||
"download": "Preuzmi",
|
||||
"path": "Mjesto",
|
||||
"size": "Veličina",
|
||||
"delete": "Izbriši",
|
||||
"backupTask": "Pokrenut je zadatak sigurnosnog kopiranja.",
|
||||
"destroyBackup": "Izbrisati sigurnosnu kopiju \" + file_to_del + \"?",
|
||||
"confirmDelete": "Želite li izbrisati ovu sigurnosnu kopiju? Ovo se ne može poništiti.",
|
||||
"confirm": "Potvrdi",
|
||||
"options": "Mogućnosti"
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "Upravitelj datoteka ne radi bez JavaScripta",
|
||||
"error": "Pogreška pri preuzimanju datoteka",
|
||||
"files": "Datoteke",
|
||||
"default": "Zadano",
|
||||
"save": "Spremi",
|
||||
"editingFile": "Uređivanje datoteke",
|
||||
"delete": "Izbriši",
|
||||
"createFile": "Stvori datoteku",
|
||||
"createDir": "Stvori direktorij",
|
||||
"rename": "Preimenuj",
|
||||
"createFileQuestion": "Kako želite nazvati novu datoteku?",
|
||||
"createDirQuestion": "Kako želite nazvati novi direktorij?",
|
||||
"renameItemQuestion": "Kako glasi novi naziv?",
|
||||
"deleteItemQuestion": "Jeste li sigurni da želite izbrisati \" + name + \"?",
|
||||
"deleteItemQuestionMessage": "Brišete \\\"\" + path + \"\\\"!<br/><br/>Ova radnja je nepovratna i datoteka će biti zauvijek izgubljena!",
|
||||
"yesDelete": "Da, razumijem posljedice",
|
||||
"noDelete": "Ne",
|
||||
"unsupportedLanguage": "Upozorenje: ova vrsta datoteke nije podržana",
|
||||
"keybindings": "Tipkovnički prečaci",
|
||||
"fileReadError": "Pogreška pri čitanju datoteke",
|
||||
"upload": "Prenesi",
|
||||
"unzip": "Raspakiraj",
|
||||
"clickUpload": "Kliknite ovdje za odabir vaših datoteka",
|
||||
"uploadTitle": "Prenesite datoteke na: ",
|
||||
"waitUpload": "Molimo pričekajte dok prenosimo vaše datoteke... Ovo može potrajati.",
|
||||
"stayHere": "NE NAPUŠTAJTE OVU STRANICU!",
|
||||
"close": "Zatvori",
|
||||
"download": "Preuzmi"
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "Ime poslužitelja",
|
||||
"serverNameDesc": "Kako želite nazvati ovaj poslužitelj",
|
||||
"serverPath": "Radni direktorij poslužitelja",
|
||||
"serverPathDesc": "Apsolutna putanja (ne uključuje izvršnu datoteku)",
|
||||
"serverLogLocation": "Lokacija zapisnika poslužitelja",
|
||||
"serverLogLocationDesc": "Apsolutna putanja datoteke zapisnika",
|
||||
"serverExecutable": "Izvršitelj poslužitelja",
|
||||
"serverExecutableDesc": "Izvršna datoteka poslužitelja",
|
||||
"serverExecutionCommand": "Naredba izvršenja poslužitelja",
|
||||
"serverExecutionCommandDesc": "Ono što će biti pokrenuto u skrivenom terminalu",
|
||||
"serverStopCommand": "Naredba zaustavljanja poslužitelja",
|
||||
"serverStopCommandDesc": "Naredba koja uvjetuje zaustavljanje programa",
|
||||
"serverAutostartDelay": "Odgoda automatskog pokretanja poslužitelja",
|
||||
"serverAutostartDelayDesc": "Odgoda prije automatskog pokretanja (ako je omogućeno ispod)",
|
||||
"serverIP": "IP poslužitelja",
|
||||
"serverIPDesc": "IP na koji će se Crafty povezati radi skupljanja statistika (Ukoliko naiđete na problem, probajte upisati pravu IP adresu umjesto 127.0.0.1)",
|
||||
"serverPort": "Port poslužitelja",
|
||||
"serverPortDesc": "Port na koji će se Crafty povezati radi skupljanja statistika",
|
||||
"removeOldLogsAfter": "Uklonite stare zapise nakon",
|
||||
"removeOldLogsAfterDesc": "Nakon koliko dana bi se datoteka zapisa trebala izbrisati (0 za isključenje ove mogućnosti)",
|
||||
"serverAutoStart": "Automatsko pokretanje poslužitelja",
|
||||
"serverCrashDetection": "Otkrivanje rušenja poslužitelja",
|
||||
"save": "Spremi",
|
||||
"cancel": "Odustani",
|
||||
"deleteServer": "Izbriši poslužitelj",
|
||||
"stopBeforeDeleting": "Molimo zaustavite poslužitelj prije brisanja",
|
||||
"exeUpdateURLDesc": "URL za izravno preuzimanje ažuriranja.",
|
||||
"exeUpdateURL": "URL izvršnog ažuriranja poslužitelja",
|
||||
"update": "Ažuriraj izvršnu datoteku",
|
||||
"bePatientUpdate": "Molimo budite strpljivi dok ažuriramo poslužitelj. Vrijeme preuzimanja može varirati ovisno o vašoj brzini interneta.<br /> Zaslon će se osvježiti za par trenutaka",
|
||||
"sendingRequest": "Slanje vašeg zahtjeva...",
|
||||
"deleteServerQuestion": "Izbrisati poslužitelj?",
|
||||
"deleteServerQuestionMessage": "Jeste li sigurni da želite izbrisati ovaj poslužitelj? Nakon ovoga nema povratka...",
|
||||
"yesDelete": "Da, izbriši",
|
||||
"noDelete": "Ne, vrati se nazad",
|
||||
"deleteFilesQuestion": "Izbrisati datoteke poslužitelja sa uređaja?",
|
||||
"deleteFilesQuestionMessage": "Želite li da Crafty izbriše sve datoteke poslužitelja s glavnog računala?",
|
||||
"yesDeleteFiles": "Da, izbriši datoteke",
|
||||
"noDeleteFiles": "Ne, samo ih uklonite s panela",
|
||||
"sendingDelete": "Brisanje poslužitelja",
|
||||
"bePatientDelete": "Molimo budite strpljivi dok uklanjamo vaš poslužitelj s Crafty panela. Zaslon će se osvježiti za par trenutaka.",
|
||||
"bePatientDeleteFiles" : "Molimo budite strpljivi dok uklanjamo vaš poslužitelj s Crafty panela i brišemo sve datoteke. Zaslon će se osvježiti za par trenutaka."
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Područje konfiguracije poslužitelja",
|
||||
"desc": "Ovdje možete promijeniti konfiguraciju vašeg poslužitelja",
|
||||
"perms": [
|
||||
"Preporuča se <code>NE</code> mijenjati putanju poslužitelja kojim upravlja Crafty.",
|
||||
"Mijenjanje putanje <code>MOŽE</code> utjecati na pravilno funkcioniranje poslužitelja, posebice na operativnim sustavima poput Linuxa, gdje su dozvole datoteka ograničene.",
|
||||
"<br /><br/>",
|
||||
"Ako smatrate da morate promijeniti mjesto na kojem se poslužitelj nalazi, to možete učiniti pod uvjetom da \"crafty\" korisnik ima dopuštenje čitanja/pisanja na zadanoj putanji.",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"Na Linuxu je to najlakše postići izvršavanjem sljedećih komandi:<br />",
|
||||
"<code>",
|
||||
" sudo chown crafty:crafty /putanja/vašeg/servera -R<br />",
|
||||
" sudo chmod 2775 /putanja/vašeg/servera -R<br />",
|
||||
"</code>"
|
||||
]
|
||||
},
|
||||
"panelConfig": {
|
||||
"save": "Spremi",
|
||||
"cancel": "Odustani",
|
||||
"delete": "Izbriši"
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
"decimal": "",
|
||||
"emptyTable": "Nema dostupnih podataka u tablici",
|
||||
"info": "Prikazuje se _START_ do _END_ od _TOTAL_ unosa",
|
||||
"infoEmpty": "Prikazuje se 0 do 0 od 0 unosa",
|
||||
"infoFiltered": "(filtrirano od ukupno _MAX_ unosa)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Prikaži _MENU_ unose",
|
||||
"loadingRecords": "Učitavanje...",
|
||||
"processing": "Obrada...",
|
||||
"search": "Pretraži:",
|
||||
"zeroRecords": "Nisu pronađeni odgovarajući zapisi",
|
||||
"paginate": {
|
||||
"first": "Prvi",
|
||||
"last": "Zadnji",
|
||||
"next": "Sljedeći",
|
||||
"previous": "Prethodni"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": aktiviraj za sortiranje stupca uzlazno",
|
||||
"sortDescending": ": aktiviraj za sortiranje stupca silazno"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "Zbirka <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
|
||||
"colvis": "Vidljivost stupca",
|
||||
"colvisRestore": "Povratak vidljivosti",
|
||||
"copy": "Kopiraj",
|
||||
"copyKeys": "Pritisnite ctrl ili u2318 + C da kopirate podatke tablice u međuspremnik sustava.<br><br>Da biste odustali, kliknite ovu poruku ili pritisnite tipku escape.",
|
||||
"copySuccess": {
|
||||
"1": "1 red je kopiran u međuspremnik",
|
||||
"_": "Kopirana %d redaka u međuspremnik"
|
||||
},
|
||||
"copyTitle": "Kopirati u međuspremnik",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Prikaži sve retke",
|
||||
"1": "Prikaži 1 red",
|
||||
"_": "Prikaži %d redaka"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Isprintaj"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"0": "Kliknite na red da biste ga odabrali",
|
||||
"1": "%d red odabran",
|
||||
"_": "%d redaka odabrana"
|
||||
},
|
||||
"cells": {
|
||||
"0": "Kliknite na ćeliju da biste je odabrali",
|
||||
"1": "%d ćelija odabrana",
|
||||
"_": "%d ćelija odabrano"
|
||||
},
|
||||
"columns": {
|
||||
"0": "Kliknite na stupac da biste ga odabrali",
|
||||
"1": "%d stupac odabran",
|
||||
"_": "%d stupaca odabrano"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"doesNotWorkWithoutJavascript": "<strong>Upozorenje: </strong>Crafty ne radi ispravno kada JavaScript nije omogućen!"
|
||||
}
|
||||
}
|
346
app/translations/it_IT.json
Normal file
@ -0,0 +1,346 @@
|
||||
{
|
||||
"login": {
|
||||
"forgotPassword": "Password dimenticata",
|
||||
"login": "Log In",
|
||||
"password": "Password",
|
||||
"username": "Nome utente"
|
||||
},
|
||||
"error": {
|
||||
"hereIsTheError": "Ecco l'errore",
|
||||
"contact": "Contatta l'assistenza di Crafty Controller su Discord!",
|
||||
"terribleFailure": "Che crash epico!",
|
||||
"embarassing": "Oh mamma, beh, questo è imbarazzante.",
|
||||
"error": "Errore!",
|
||||
"start-error": "Il server {} non è partito, con codice errore: {}",
|
||||
"closedPort": "Ci siamo accorti che la porta {} potrebbe non essere aperta, o un firewall potrebbe tenerla chiusa. Le connessioni remote al server potrebbero essere limitate.",
|
||||
"internet": "Ci siamo accorti che il server che fa girare Crafty non ha connessione a internet. Le connessioni dei Client potrebbero essere limitate.",
|
||||
"eulaTitle": "Accetta il Contratto con l'Utente Finale",
|
||||
"eulaMsg": "Devi accettare l'EULA. Una copia dell'EULA di Mojang è allegata sotto questo messaggio.",
|
||||
"eulaAgree": "Accetti l'EULA?"
|
||||
},
|
||||
"404": {
|
||||
"contact": "Contatta l'assistenza di Crafty Controller su Discord!",
|
||||
"unableToFind": "We were unable to find the page you are looking for. Please try again, or go back and refresh.",
|
||||
"notFound": "Pagina non trovata"
|
||||
},
|
||||
"footer": {
|
||||
"version": "Versione",
|
||||
"copyright": "Copyright",
|
||||
"allRightsReserved": "Tutti i diritti riservati"
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "Dashboard",
|
||||
"servers": "Servers",
|
||||
"documentation": "Documentazione",
|
||||
"credits": "Crediti",
|
||||
"contribute": "Contribuisci",
|
||||
"newServer": "Crea un nuovo Server",
|
||||
"navigation": "Navigazione"
|
||||
},
|
||||
"serverWizard": {
|
||||
"newServer": "Crea un nuovo Server",
|
||||
"importServer": "Importa un Server esistente",
|
||||
"importZip": "Importa da un file Zip",
|
||||
"serverName": "Nome Server",
|
||||
"serverPath": "Percorso del Server",
|
||||
"serverType": "Tipo di Server",
|
||||
"selectType": "Seleziona un tipo",
|
||||
"serverVersion": "Versione Server",
|
||||
"selectVersion": "Seleziona un Server",
|
||||
"absoluteServerPath": "Percorso assoluto del server",
|
||||
"serverJar": "JarFile del server",
|
||||
"minMem": "Memoria Minima",
|
||||
"maxMem": "Memoria Massima",
|
||||
"serverPort": "Porta del Server",
|
||||
"defaultPort": "25565 default",
|
||||
"sizeInGB": "Dimensione in GB",
|
||||
"zipPath": "Percorso Server",
|
||||
"absoluteZipPath": "Percorso assoluto del tuo Server in Zip",
|
||||
"resetForm": "Ripulisci il form",
|
||||
"importServerButton": "Importa Server!",
|
||||
"buildServer": "Costruisci il Server!",
|
||||
"quickSettings": "Impostazioni rapide",
|
||||
"quickSettingsDescription": "Non preoccuparti, puoi cambiare queste impostazioni in seguito",
|
||||
"myNewServer": "Il mio nuovo server",
|
||||
"bePatient": "Per favore sii paziente mentre ' + (importing ? 'importiamo' : 'scarichiamo') + ' il server",
|
||||
"importing": "Importando il Server...",
|
||||
"downloading": "Scaricando il Server...",
|
||||
"addRole": "Aggiungi il Server a Ruoli Esistenti",
|
||||
"autoCreate": "Se non sono selezionati, Crafty ne creerà uno!",
|
||||
"selectRole": "Seleziona il o i ruoli"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Dashboard",
|
||||
"memUsage": "Uso della Memoria",
|
||||
"cpuUsage": "Uso della CPU",
|
||||
"host": "Host",
|
||||
"players": "Giocatori",
|
||||
"backups": "Backups",
|
||||
"newServer": "Crea un nuovo Server",
|
||||
"allServers": "Tutti i Servers",
|
||||
"server": "Server",
|
||||
"actions": "Azioni",
|
||||
"world": "Mondo",
|
||||
"motd": "Slogan",
|
||||
"version": "Versione",
|
||||
"status": "Stato",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"lastBackup": "Ultimo:",
|
||||
"nextBackup": "Prossimo:",
|
||||
"servers": "Server",
|
||||
"cannotSeeOnMobile": "Non vedi nulla da mobile?",
|
||||
"cannotSee": "Non vedi nulla?",
|
||||
"cannotSeeOnMobile2": "Prova a scorrere sulla tabella lateralmente.",
|
||||
"max": "Massimo",
|
||||
"avg": "Media",
|
||||
"bePatientStart": "Per favore sii paziente mentre accendiamo il server.<br /> Questa schermata si aggiornerà in un momento",
|
||||
"bePatientStop": "Per favore sii paziente mentre spegniamo il server.<br /> Questa schermata si aggiornerà in un momento",
|
||||
"bePatientRestart": "Per favore sii paziente mentre riavviamo il server.<br /> Questa schermata si aggiornerà in un momento",
|
||||
"bePatientClone": "Per favore sii paziente mentre cloniamo il server.<br /> Questa schermata si aggiornerà in un momento",
|
||||
"sendingCommand": "Inviando il tuo comando",
|
||||
"cpuCurFreq": "Frequenza Attuale della CPU",
|
||||
"cpuMaxFreq": "Frequenza Massima della CPU",
|
||||
"cpuCores": "Core della CPU",
|
||||
"start": "Avvia",
|
||||
"stop": "Ferma",
|
||||
"clone": "Clona",
|
||||
"kill": "Termina il processo",
|
||||
"restart": "Riavvia",
|
||||
"killing": "Terminando il processo...",
|
||||
"starting": "Avvio ritardato",
|
||||
"delay-explained": "Il servizio è partito di recente e sta ritardando l'avvio dell'istanza del Server di Minecraft",
|
||||
"no-servers": "Attualmente non ci sono server. Per cominciare, clicca",
|
||||
"welcome": "Benvenuto su Crafty Controller"
|
||||
},
|
||||
"accessDenied": {
|
||||
"accessDenied": "Accesso negato",
|
||||
"noAccess": "Non hai accesso a questa risorsa",
|
||||
"contactAdmin": "Contatta l'amministratore del tuo server per accedere a questa risorsa, o, se pensi tu debba avere accesso a tale risorsa, contatta il supporto.",
|
||||
"contact": "Contatta il supporto di Crafty Control tramite Discord"
|
||||
},
|
||||
"serverStats": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"serverStatus": "Stato del server",
|
||||
"serverStarted": "Server Avviato",
|
||||
"serverUptime": "Tempo di esecuzione",
|
||||
"players": "Giocatori",
|
||||
"memUsage": "Utilizzo della Memoria",
|
||||
"cpuUsage": "Utilizzo della CPU",
|
||||
"version": "Versione",
|
||||
"description": "Descrizione",
|
||||
"errorCalculatingUptime": "Errore nel calcolare il periodo di esecuzione del server",
|
||||
"serverTime": "Zona UTC",
|
||||
"unableToConnect": "Non riesco a connettermi"
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "Dettagli del Server",
|
||||
"terminal": "Terminale",
|
||||
"logs": "Logs",
|
||||
"schedule": "Pianifica",
|
||||
"backup": "Backup",
|
||||
"files": "Files",
|
||||
"config": "Config",
|
||||
"playerControls": "Player Management"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "Ferma lo scorrimento automatico",
|
||||
"commandInput": "Inserisci il tuo comando",
|
||||
"sendCommand": "Invia il comando",
|
||||
"start": "Avvia",
|
||||
"restart": "Riavvia",
|
||||
"stop": "Ferma",
|
||||
"updating": "Aggiornando...",
|
||||
"starting": "Avvio ritardato",
|
||||
"delay-explained": "Il servizio è partito di recente e sta ritardando l'avvio dell'istanza del Server di Minecraft"
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "Giocatori",
|
||||
"bannedPlayers": "Giocatori Bannati",
|
||||
"loadingBannedPlayers": "Carico i Giocatori Bannati"
|
||||
},
|
||||
"serverBackups": {
|
||||
"backupNow": "Fai un Backup ora!",
|
||||
"backupAtMidnight": "Backup automatico a mezzanotte?",
|
||||
"storageLocation": "Percorso del backup",
|
||||
"storageLocationDesc": "Dove vuoi memorizzare i backup?",
|
||||
"maxBackups": "Backup massimi",
|
||||
"maxBackupsDesc": "Crafty non immagazzinerà più di N backup, cancellerà quelli più vecchi (inserisci 0 per mantenerli tutti)",
|
||||
"save": "Salva",
|
||||
"cancel": "Cancella",
|
||||
"currentBackups": "Backup attuali",
|
||||
"download": "Download",
|
||||
"path": "Percorso",
|
||||
"size": "Dimensione",
|
||||
"delete": "Cancella",
|
||||
"backupTask": "È cominciato un processo di backup.",
|
||||
"destroyBackup": "Cancellare backup \" + file_to_del + \"?",
|
||||
"confirmDelete": "Vuoi cancellare questo Backup? Questa azione non è annullabile.",
|
||||
"confirm": "Conferma",
|
||||
"options": "Opzioni"
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "Il File Manager non funziona senza JavaScript",
|
||||
"error": "Errore nel recuperare files",
|
||||
"files": "Files",
|
||||
"default": "Default",
|
||||
"save": "Salva",
|
||||
"editingFile": "Modificare file",
|
||||
"delete": "Cancella",
|
||||
"createFile": "Crea file",
|
||||
"createDir": "Crea Cartella",
|
||||
"rename": "Rinomina",
|
||||
"createFileQuestion": "Che nome vuoi dare al nuovo file?",
|
||||
"createDirQuestion": "Che nome vuoi dare alla nuova cartella?",
|
||||
"renameItemQuestion": "Qual è il nuovo nome?",
|
||||
"deleteItemQuestion": "Sei sicuro di voler eliminare \" + name + \"?",
|
||||
"deleteItemQuestionMessage": "Stai eliminando \\\"\" + path + \"\\\"!<br/><br/>Questa azione è irreversibile e non potrai tornare indietro!",
|
||||
"yesDelete": "Sì, capisco le conseguenze",
|
||||
"noDelete": "No",
|
||||
"unsupportedLanguage": "Attenzione: Questo non è un tipo di file supportato",
|
||||
"keybindings": "Combinazioni di tasti",
|
||||
"fileReadError": "Errore nel leggere i file",
|
||||
"upload": "Carica",
|
||||
"unzip": "Estrai",
|
||||
"clickUpload": "Clicca qui per selezionare i tuoi file",
|
||||
"uploadTitle": "Carica i file: ",
|
||||
"waitUpload": "Per favore attendi mentre carichiamo i tuoi file... Potrebbe volerci un po' di tempo.",
|
||||
"stayHere": "NON ABBANDONARE QUESTA PAGINA!",
|
||||
"close": "Chiudi",
|
||||
"download": "Scarica"
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "Nome del Server",
|
||||
"serverNameDesc": "Come vorresti chiamare questo server",
|
||||
"serverPath": "Cartella di Operazioni del Server",
|
||||
"serverPathDesc": "Percorso Assoluto per Intero (Escludendo l'eseguibile)",
|
||||
"serverLogLocation": "Posizione del Log del Server",
|
||||
"serverLogLocationDesc": "Percorso Assoluto per Intero del File di Log",
|
||||
"serverExecutable": "Eseguibile del Server",
|
||||
"serverExecutableDesc": "Il file Eseguibile del server",
|
||||
"serverExecutionCommand": "Comando di Esecuzione del Server",
|
||||
"serverExecutionCommandDesc": "Quello che verrà eseguito in un terminale nascosto",
|
||||
"serverStopCommand": "Comando per terminare il server",
|
||||
"serverStopCommandDesc": "Comando da inviare al server per fermarlo",
|
||||
"serverAutostartDelay": "Ritardo Automatico dell'Avvio",
|
||||
"serverAutostartDelayDesc": "Il ritardo per far partire il server (Se abilitato più sotto)",
|
||||
"serverIP": "Server IP",
|
||||
"serverIPDesc": "L'IP a cui Crafty dovrebbe connettersi per le statistiche (Prova un IP reale piuttosto di 127.0.0.1 se hai qualche problema)",
|
||||
"serverPort": "Porta del Server",
|
||||
"serverPortDesc": "La Porta a cui Crafty dovrebbe connettersi per le statistiche",
|
||||
"removeOldLogsAfter": "Rimuovi log più vecchi di ",
|
||||
"removeOldLogsAfterDesc": "Di quanti giorni dovrebbe essere vecchio un log per essere cancellato (0 per non cancellarne)",
|
||||
"serverAutoStart": "Avvio automatico del Server",
|
||||
"serverCrashDetection": "Rilevamento dei Crash del Server",
|
||||
"save": "Salva",
|
||||
"cancel": "Cancella",
|
||||
"deleteServer": "Elimina Server",
|
||||
"stopBeforeDeleting": "Per favore ferma il Server prima di eliminarlo",
|
||||
"exeUpdateURLDesc": "URL Diretto per il download di aggiornamenti.",
|
||||
"exeUpdateURL": "URL per gli aggiornamenti dell'Eseguibile del Server",
|
||||
"update": "Aggiorna l'Eseguibile",
|
||||
"bePatientUpdate": "Per favore sii paziente mentre aggiorniamo il server. I tempi di scaricamento possono variare in base alla tua velocità di Internet.<br /> Questa schermata si aggiornerà in un momento.",
|
||||
"sendingRequest": "Invio la tua richiesta...",
|
||||
"deleteServerQuestion": "Eliminare il Server?",
|
||||
"deleteServerQuestionMessage": "Sei sicuro di voler eliminare questo server? Dopo la conferma non si può tornare indietro...",
|
||||
"yesDelete": "Sì, eliminalo",
|
||||
"noDelete": "No, torna indietro",
|
||||
"deleteFilesQuestion": "Eliminare i file del Server dalla macchina?",
|
||||
"deleteFilesQuestionMessage": "Vorresti che Crafty eliminasse tutti i file del Server dalla macchina Host?",
|
||||
"yesDeleteFiles": "Sì, cancella i file",
|
||||
"noDeleteFiles": "No, rimuovili dal pannello e basta",
|
||||
"sendingDelete": "Eliminando il Server",
|
||||
"bePatientDelete": "Per favore sii paziente mentre rimuoviamo il server dal pannello. Questa schermata si aggiornerà in un momento.",
|
||||
"bePatientDeleteFiles" : "Per favore sii paziente mentre rimuoviamo il server dal pannello ed eliminiamo i file. Questa schermata sparirà in un momento."
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Area di Configurazione del Server",
|
||||
"desc": "Qui è dove puoi cambiare la configurazione del tuo Server",
|
||||
"perms": [
|
||||
"È raccomandabile <code>NON</code> cambiare i percorsi di un Server gestito da Crafty.",
|
||||
"Cambiare i percorsi <code>PUO'</code> rompere cose, specialmente su sistemi tipo Linux dove i permessi dei file sono più restrittivi.",
|
||||
"<br /><br/>",
|
||||
"Se credi di dover cambiare il percorso di un server puoi farlo, a patto di dare all'utente \"crafty\" i permessi di leggere e scrivere nel percorso del server.",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"Su Linux questa operazione si esegue con questi comandi:<br />",
|
||||
"<code>",
|
||||
" sudo chown crafty:crafty /percorso/al/tuo/server -R<br />",
|
||||
" sudo chmod 2775 /percorso/al/tuo/server -R<br />",
|
||||
"</code>"
|
||||
]
|
||||
},
|
||||
"panelConfig": {
|
||||
"save": "Salva",
|
||||
"cancel": "Cancella",
|
||||
"delete": "Elimina"
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
"decimal": "",
|
||||
"emptyTable": "Nessun dato disponibile nella tabella",
|
||||
"info": "Visualizzando da _START_ fino a _END_ di _TOTAL_ voci",
|
||||
"infoEmpty": "Visualizzando da 0 fino a 0 di 0 voci",
|
||||
"infoFiltered": "(Filtrato da_MAX_ voci totali)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Mostra le voci di _MENU_ ",
|
||||
"loadingRecords": "Caricamento...",
|
||||
"processing": "Caricamento...",
|
||||
"search": "Cerca:",
|
||||
"zeroRecords": "Nessuna voce corrispondente trovata",
|
||||
"paginate": {
|
||||
"first": "Primo",
|
||||
"last": "Ultimo",
|
||||
"next": "Prossima",
|
||||
"previous": "Precedente"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": attiva per ordinare le colonne in modo ascendente",
|
||||
"sortDescending": ": attiva per ordinare le colonne in modo discendente"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "Collezione <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
|
||||
"colvis": "Visibilità della colonna",
|
||||
"colvisRestore": "Ripristina Visibilità",
|
||||
"copy": "Copia",
|
||||
"copyKeys": "Premi Ctrl o u2318 + C per copiare i dati della tabella negli appunti di sistema.<br><br> Per cancellare, clicca questo messaggio o premi ESC.",
|
||||
"copySuccess": {
|
||||
"1": "Copiata 1 riga negli appunti",
|
||||
"_": "Copiate %d righe negli appunti"
|
||||
},
|
||||
"copyTitle": "Copia negli appunti",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Mostra tutte le righe",
|
||||
"1": "Mostra 1 riga",
|
||||
"_": "Mostra %d righe"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Print"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"0": "Click on a row to select it",
|
||||
"1": "%d row selected",
|
||||
"_": "%d rows selected"
|
||||
},
|
||||
"cells": {
|
||||
"0": "Click on a cel to select it",
|
||||
"1": "%d cell selected",
|
||||
"_": "%d cells selected"
|
||||
},
|
||||
"columns": {
|
||||
"0": "Click on a column to select it",
|
||||
"1": "%d column selected",
|
||||
"_": "%d columns selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"doesNotWorkWithoutJavascript": "<strong>Warning: </strong>Crafty doesn't work properly when JavaScript isn't enabled!"
|
||||
}
|
||||
}
|
347
app/translations/lol_EN.json
Normal file
@ -0,0 +1,347 @@
|
||||
{
|
||||
"login": {
|
||||
"forgotPassword": "FORGWOTS YOUR SEEKRET",
|
||||
"login": "WOG INZ",
|
||||
"password": "SEEKRET",
|
||||
"username": "USERNAEM"
|
||||
},
|
||||
"error": {
|
||||
"hereIsTheError": "HER IZ TEH OOF",
|
||||
"contact": "CONTACK CWAFTY CONTROLLR SUPORT ON DA DIZORD",
|
||||
"terribleFailure": "SUMTIN DUN BWOKE",
|
||||
"embarassing": "OH MAH, WELL, DIS AR TEH EMBARRASIN.",
|
||||
"error": "BIG OOF!",
|
||||
"start-error": "CHAIR {} FAILD 2 START WIF OOF CODE: {}",
|
||||
"portReminder": "WE HAS DETECTD DIS AR TEH FURST TIEM {} IZ BEAN RUN. IF U WANTS IT ACESIBLE TO NEIGHBORHOOD CATS PLZ UNLOCK CAT_FLAP, {}, THRU UR ROUTR IF U HAS NOT DUN SO.",
|
||||
"internet": "WE HAS DETECTD TEH BIG BOX RUNNIN CRAFTY HAS NO CONNECSHUN 2 TEH INTERNET. HOOMAN CONNECSHUNS 2 TEH SERVR CUD BE LIMITD.",
|
||||
"eulaTitle": "SAYZ YESH TWOO TEH LEGAL-WEEGALS",
|
||||
"eulaMsg": "U MUST SAY YESH. COPY OV TEH MOJANG EULA IZ LINKD UNDR DIS MESAGE.",
|
||||
"eulaAgree": "DOZ HOOMAN AGWEE",
|
||||
"noJava": "CHAIR {} FAILD 2 GO WIF OOF CODE: WE HAS DETECTD JAVA IZ NOT HEREZ. PLZ BRING UZ JAVA DEN START TEH SERVR."
|
||||
},
|
||||
"404": {
|
||||
"contact": "CONTACK CWAFTY CONTROLLR SUPORT ON DA DWISCORDZ",
|
||||
"unableToFind": "WE WUZ UNABLE 2 FIND TEH PAEG U R LOOKIN 4. PLZ TRY AGAIN, OR GO BAK AN REFRESH.",
|
||||
"notFound": "PAEG NOT FINDZ"
|
||||
},
|
||||
"footer": {
|
||||
"version": "VERSHUN",
|
||||
"copyright": "COPYRIGHT",
|
||||
"allRightsReserved": "ALL FISH MINEZ"
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "DASHBORD",
|
||||
"servers": "SERVRS",
|
||||
"documentation": "DOCUMENTASHUN",
|
||||
"credits": "GUD HOOMANS",
|
||||
"contribute": "THROW MONZ",
|
||||
"newServer": "CONSTWUCT A SERVR",
|
||||
"navigation": "NAVIGASHUN"
|
||||
},
|
||||
"serverWizard": {
|
||||
"newServer": "Maek a nu servr",
|
||||
"importServer": "Addz servr to Cwafty",
|
||||
"importZip": "Addz servr from Squeezd Box",
|
||||
"serverName": "Servr Naym",
|
||||
"serverPath": "Servr Peth",
|
||||
"serverType": "Servr Typ",
|
||||
"selectType": "Wut typ ov servr",
|
||||
"serverVersion": "Servr Verzhun",
|
||||
"selectVersion": "How old iz servr?",
|
||||
"absoluteServerPath": "Wer iz servr? (Absolute peth)",
|
||||
"serverJar": "Servr Jafarfile",
|
||||
"minMem": "Smol Memz Limit",
|
||||
"maxMem": "BIG Memz Limit",
|
||||
"serverPort": "Catflap",
|
||||
"defaultPort": "25565 iz nermalz choicez",
|
||||
"sizeInGB": "Siez in Gigabitez",
|
||||
"zipPath": "Wer iz Squeezed Box (Peth)",
|
||||
"absoluteZipPath": "Absolute peth 2 ur tiny box",
|
||||
"resetForm": "Rezet",
|
||||
"importServerButton": "Get a servr",
|
||||
"buildServer": "Maek a servr!",
|
||||
"quickSettings": "FAST SETTINGZ!",
|
||||
"quickSettingsDescription": "NO FEAR, IT NOT PERMZ",
|
||||
"myNewServer": "MY NEW SOFT CHAIR",
|
||||
"bePatient": "Plz be paitentz me iz ' + (importing ? 'claimzin' : 'findin') + ' teh warm comfy servrz",
|
||||
"importing": "Claimzin servrz...",
|
||||
"downloading": "Huntin fur gud servr...",
|
||||
"addRole": "Maek servr huv Existing Role(s)",
|
||||
"autoCreate": "If none r selectd me wil maek wan, I Gochu!",
|
||||
"selectRole": "Chooz Role(s)"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "DASHBORD",
|
||||
"memUsage": "MEMZ USAGE",
|
||||
"cpuUsage": "CPUZ USAGE",
|
||||
"host": "FAVORITE BOX",
|
||||
"players": "HOOMANZ",
|
||||
"backups": "BAKUPS",
|
||||
"newServer": "Maek new Servr",
|
||||
"allServers": "Pak of Servrs",
|
||||
"server": "SERVRS",
|
||||
"actions": "ACSHUNS",
|
||||
"world": "WURLD",
|
||||
"motd": "MOTD",
|
||||
"version": "VERZHUN",
|
||||
"status": "STATUZ",
|
||||
"online": "Turnd on",
|
||||
"offline": "Turnd off",
|
||||
"lastBackup": "Last:",
|
||||
"nextBackup": "Next:",
|
||||
"servers": "Servrs",
|
||||
"cannotSeeOnMobile": "U Havin problermz seeun tiny scrienz?",
|
||||
"cannotSee": "U Havin problermz seeun?",
|
||||
"cannotSeeOnMobile2": "Try scrollin teh tablez sidewayz.",
|
||||
"max": "Max",
|
||||
"avg": "Avg",
|
||||
"bePatientStart": "Plz be paitentz we startin teh servr.<br /> Dis skreehn WILL refursh in momentz",
|
||||
"bePatientStop": "Plz be paitentz we stoppin teh servr.<br /> Dis scrien will refursh in momentz",
|
||||
"bePatientRestart": "Plz be paitentz we restartin teh servr.<br /> Dis skreehn will refursh in momentz",
|
||||
"bePatientClone": "Plz be paitentz we clone teh server.<br /> Dis scrien will refursh in momentz",
|
||||
"sendingCommand": "Sendz ur comarned",
|
||||
"cpuCurFreq": "Hamstur(cpu) speedz right naow",
|
||||
"cpuMaxFreq": "Hamstur(cpu) Omglimited speed",
|
||||
"cpuCores": "Numbr ov hamstur wheelz",
|
||||
"start": "Maek go",
|
||||
"stop": "Stahp Plz",
|
||||
"clone": "Copyz",
|
||||
"kill": "Eatz teh process",
|
||||
"restart": "Restart",
|
||||
"killing": "Maekin process go bye...",
|
||||
"starting": "I waitz b4 I start",
|
||||
"delay-explained": "Teh service/agent hus resently startd an iz delayin teh startd ov teh servr",
|
||||
"no-servers": "Thar R no servrs. Big sad :( 2 git startd nd maek servr, click",
|
||||
"welcome": "Welcom 2 Cwafty Controllr"
|
||||
},
|
||||
"accessDenied": {
|
||||
"accessDenied": "Acces Denid",
|
||||
"noAccess": "U do not haz acces 2 dis litterbox",
|
||||
"contactAdmin": "Contakt ur servr adminz 4 acces 2 dis litterbox, or if u finkz u sud has acces, contakt suport",
|
||||
"contact": "Contack Cwafty Controllr Suport on da Dwiscordz"
|
||||
},
|
||||
"serverStats": {
|
||||
"online": "Turnd on",
|
||||
"offline": "Turnd off",
|
||||
"serverStatus": "Servr Status",
|
||||
"serverStarted": "Servr Started",
|
||||
"serverUptime": "How longz servr been awaek",
|
||||
"players": "Hoomanz",
|
||||
"memUsage": "How hard iz thinkin rn",
|
||||
"cpuUsage": "CPU Usage",
|
||||
"version": "Verzhun",
|
||||
"description": "Descripshun",
|
||||
"errorCalculatingUptime": "I maek oof wen calculatin teh teim awaek!",
|
||||
"serverTime": "UTC Teim",
|
||||
"unableToConnect": "Not today... :("
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "Servr infoz",
|
||||
"terminal": "Cmds go here",
|
||||
"logs": "Logz",
|
||||
"schedule": "Schdulez",
|
||||
"backup": "Bakup",
|
||||
"files": "Filez",
|
||||
"config": "Settingz Wurld",
|
||||
"playerControls": "Hooman managementz"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "Stahp teh scrolin",
|
||||
"commandInput": "Entr your comarned",
|
||||
"sendCommand": "Send comarned",
|
||||
"start": "Maek go",
|
||||
"restart": "Copyz",
|
||||
"stop": "Stahp Plz",
|
||||
"updating": "Plz waitz...",
|
||||
"starting": "I waitz b4 I start",
|
||||
"delay-explained": "Teh service/agent hus resently startd an iz delayin teh startd ov teh servr"
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "Hoomans",
|
||||
"bannedPlayers": "Naughty Hoomans",
|
||||
"loadingBannedPlayers": "Plz wait, I telz u abot teh bad hoomanz"
|
||||
},
|
||||
"serverBackups": {
|
||||
"backupNow": "Bakup nowz!",
|
||||
"backupAtMidnight": "Auto-bakup at middlenightz?",
|
||||
"storageLocation": "Shiny Stash ov hingz",
|
||||
"storageLocationDesc": "Wer do u wants 2 stash bakups?",
|
||||
"maxBackups": "Max Bakups",
|
||||
"maxBackupsDesc": "Cwafty will not keepz moar than N bckups, deletin teh most oldz furst (entr 0 to be big greedy)",
|
||||
"save": "Dun",
|
||||
"cancel": "Stahp",
|
||||
"currentBackups": "Current stash ov bakups",
|
||||
"download": "Downloadz",
|
||||
"path": "Peth",
|
||||
"size": "How bigz",
|
||||
"delete": "Maek gone",
|
||||
"backupTask": "Okai I getz fish, bak soonz",
|
||||
"destroyBackup": "Eat bakup \" + file_to_del + \"?",
|
||||
"confirmDelete": "R u sure u wantz me to eatz dis bakup? Wial beh lozt forevr (longir than kittehz napz)",
|
||||
"confirm": "Yis",
|
||||
"options": "Opshuns"
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "Mr file manager needz JafarScrpt, U SHULD GET IT",
|
||||
"error": "Big oof wen I try to getz fishies",
|
||||
"files": "Fish",
|
||||
"default": "Defaultz",
|
||||
"save": "Dun",
|
||||
"editingFile": "Maek fish differentz",
|
||||
"delete": "Eat fish",
|
||||
"createFile": "Maek fish (>O.O<)",
|
||||
"createDir": "Create directory (for fish)",
|
||||
"rename": "Naym Fish",
|
||||
"createFileQuestion": "What naym u want 4 teh new file?",
|
||||
"createDirQuestion": "What naym u want 4 teh new directory?",
|
||||
"renameItemQuestion": "What should teh new naym be?",
|
||||
"deleteItemQuestion": "Are u sure u want 2 delete \" + name + \"?",
|
||||
"deleteItemQuestionMessage": "Ur deleting \\\"\" + path + \"\\\"!<br/><br/>Dis achshun will be ireeversibible an beh lozt forevr (longir than kittehz napz)",
|
||||
"yesDelete": "Yis, I promize I understanth teh consequencez",
|
||||
"noDelete": "Get me outta herez!",
|
||||
"unsupportedLanguage": "Warning: Dis no be sumtin I can readz (file typ badz)",
|
||||
"keybindings": "Pawbindz",
|
||||
"fileReadError": "I can no readz... oof",
|
||||
"upload": "Give fish",
|
||||
"unzip": "Maek Unsmol",
|
||||
"clickUpload": "Click her 2 select Ur fish",
|
||||
"uploadTitle": "Upload fish 2: ",
|
||||
"waitUpload": "Plz be paitentz we gib ur fish 2 de servr... Dis may take a while.",
|
||||
"stayHere": "U MUST STAY HERE, PLZ DNT GO!",
|
||||
"close": "Plz stahp",
|
||||
"download": "Take fish"
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "Server Naym",
|
||||
"serverNameDesc": "Wat u wish 2 nayem dis server",
|
||||
"serverPath": "Servr Wrking Dir",
|
||||
"serverPathDesc": "Absolutley full peth (not including eggs-cute-able)",
|
||||
"serverLogLocation": "Servr Log Location",
|
||||
"serverLogLocationDesc": "Absolutley full peth 2 teh log fish",
|
||||
"serverExecutable": "Servr Eggs-cute-able",
|
||||
"serverExecutableDesc": "Teh server's fish dat mak hing go brr",
|
||||
"serverExecutionCommand": "Servr Eggs-cute Cmd",
|
||||
"serverExecutionCommandDesc": "Wat 2 do in a secret unseen terminal",
|
||||
"serverStopCommand": "Servr Stahp Cmd",
|
||||
"serverStopCommandDesc": "Comarned 2 send teh program 2 stop it",
|
||||
"serverAutostartDelay": "Servr yawn Delay",
|
||||
"serverAutostartDelayDesc": "How loz 2 stay in bedz b4 mak go servr (If yis below)",
|
||||
"serverIP": "Servr IP",
|
||||
"serverIPDesc": "IP Cwafty should mak connecshun 2 fur stats (Try a real ip, no 127.0.0.1 if u hav issues)",
|
||||
"serverPort": "Servr Catflap",
|
||||
"serverPortDesc": "Catflap Cwafty wud use to fur stats",
|
||||
"removeOldLogsAfter": "Burry Old Logz After",
|
||||
"removeOldLogsAfterDesc": "How lotz in dais will a log file has 2 be 2 to git burriedz (0 iz 'treasure 4 evr')",
|
||||
"serverAutoStart": "Servr auto brrr",
|
||||
"serverCrashDetection": "Know when servr go OOF...",
|
||||
"save": "Dun",
|
||||
"cancel": "Stahp",
|
||||
"deleteServer": "Eat Server",
|
||||
"stopBeforeDeleting": "Plz stop teh servr b4 you eatz it",
|
||||
"exeUpdateURLDesc": "Fast URL to get new fish to updatz servr (Many cat go dis peth, very dirct).",
|
||||
"exeUpdateURL": "Server Eggs-cuta-ble Update Direct URL",
|
||||
"update": "Update Eggs-cuta-ble",
|
||||
"bePatientUpdate": "Plz be paitentz we get teh newz server. Hunting teimz can vary if ur interwebz iz poop.<br /> Dis scrien will refresh soonz",
|
||||
"sendingRequest": "Screaming @ catz wif ur request...",
|
||||
"deleteServerQuestion": "I can eatz Servr?",
|
||||
"deleteServerQuestionMessage": "R u sure u wants me 2 eat dis servr? After I eats tehre iz no go bak...",
|
||||
"yesDelete": "Yis, eat it",
|
||||
"noDelete": "No no no, pls go bak",
|
||||
"deleteFilesQuestion": "Eat servr fish too?",
|
||||
"deleteFilesQuestionMessage": "Wud u liek Cwafty 2 eat aw da fish dat wis in da servr? Wial beh lozt forevr (longir than kittehz napz)",
|
||||
"yesDeleteFiles": "Yis, eat dem fishies",
|
||||
"noDeleteFiles": "No, jst eat teh panelz",
|
||||
"sendingDelete": "Okei I eatz Servr, yummi",
|
||||
"bePatientDelete": "Plz be paitent I eat ur servr. Dis scrien will refresh soonz",
|
||||
"bePatientDeleteFiles" : "Plz be paitent I eat ur servr an eat aw teh yummi fish. Dis scrien will refresh soonz"
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Server Config Area",
|
||||
"desc": "Here iz where u can change teh configuration of your server (Character break dis importantz)",
|
||||
"perms": [
|
||||
"It iz recommended 2 <code>NOT</code> change teh peths of a servr managed by Cwafty. DIS IS BAD JUJU",
|
||||
"Changing peths <code>CAN</code> break things, especially on Linux typ operatin systems where file permissions are more locked down.",
|
||||
"<br /><br/>",
|
||||
"If u feel u hav 2 change where a server iz located u may do so as long as you give teh \"crafty\" user permission to read / write to teh server peth.",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"On Linux dis iz best done by executing teh following:<br />",
|
||||
"<code>",
|
||||
" sudo chown crafty:crafty /path/to/your/server -R<br />",
|
||||
" sudo chmod 2775 /path/to/your/server -R<br />",
|
||||
"</code>"
|
||||
]
|
||||
},
|
||||
"panelConfig": {
|
||||
"save": "Dun",
|
||||
"cancel": "Stahp",
|
||||
"delete": "Delet"
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
"decimal": "",
|
||||
"emptyTable": "No data available in table",
|
||||
"info": "Showing _START_ to _END_ of _TOTAL_ entries",
|
||||
"infoEmpty": "Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered": "(filtered from _MAX_ total entries)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Show _MENU_ entries",
|
||||
"loadingRecords": "Loading...",
|
||||
"processing": "Processing...",
|
||||
"search": "Search:",
|
||||
"zeroRecords": "No matching records found",
|
||||
"paginate": {
|
||||
"first": "First",
|
||||
"last": "Last",
|
||||
"next": "Next",
|
||||
"previous": "Previous"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": activate to sort column ascending",
|
||||
"sortDescending": ": activate to sort column descending"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "Collection <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
|
||||
"colvis": "Column Visibility",
|
||||
"colvisRestore": "Restore visibility",
|
||||
"copy": "Copy",
|
||||
"copyKeys": "Press ctrl or u2318 + C to copy teh table data to your system clipboard.<br><br>To cancel, click this message or press escape.",
|
||||
"copySuccess": {
|
||||
"1": "Copied 1 row to clipboard",
|
||||
"_": "Copied %d rows to clipboard"
|
||||
},
|
||||
"copyTitle": "Copy to Clipboard",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Show all rows",
|
||||
"1": "Show 1 row",
|
||||
"_": "Show %d rows"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Print"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"0": "Click on a row to select it",
|
||||
"1": "%d row selected",
|
||||
"_": "%d rows selected"
|
||||
},
|
||||
"cells": {
|
||||
"0": "Click on a cel to select it",
|
||||
"1": "%d cell selected",
|
||||
"_": "%d cells selected"
|
||||
},
|
||||
"columns": {
|
||||
"0": "Click on a column to select it",
|
||||
"1": "%d column selected",
|
||||
"_": "%d columns selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"doesNotWorkWithoutJavascript": "<strong>Warning: </strong>CWafty don't go brrrr wen JafarScwipt isn't der, U SHULD GET IT"
|
||||
}
|
||||
}
|
346
app/translations/nl_BE.json
Normal file
@ -0,0 +1,346 @@
|
||||
{
|
||||
"login": {
|
||||
"forgotPassword": "Wachtwoord vergeten",
|
||||
"login": "Log In",
|
||||
"password": "Wachtwoord",
|
||||
"username": "gebruikersnaam"
|
||||
},
|
||||
"error": {
|
||||
"hereIsTheError": "Hier is de fout",
|
||||
"contact": "Neem contact op met Crafty Control ondersteuning via Discord",
|
||||
"terribleFailure": "Wat een verschrikkelijke mislukking!",
|
||||
"embarassing": "Oh, nou, dit is gênant.",
|
||||
"error": "Fout!",
|
||||
"start-error": "Server {} kan niet starten met foutcode: {}",
|
||||
"closedPort": "We hebben gedetecteerd dat poort {} mogelijk niet open is op het hostnetwerk of dat een firewall deze blokkeert. Externe clientverbindingen met de server kunnen beperkt zijn.",
|
||||
"internet": "We hebben gedetecteerd dat de machine waarop Crafty draait geen verbinding met internet heeft. Clientverbindingen met de server kunnen beperkt zijn.",
|
||||
"eulaTitle": "Akkoord gaan met EULA",
|
||||
"eulaMsg": "U moet akkoord gaan met de EULA. Een kopie van de Mojang EULA is gelinkt onder dit bericht.",
|
||||
"eulaAgree": "Bent u het eens?"
|
||||
},
|
||||
"404": {
|
||||
"contact": "Neem contact op met Crafty Control ondersteuning via Discord",
|
||||
"unableToFind": "We hebben de pagina die u zoekt niet kunnen vinden. Probeer het opnieuw, of ga terug en herlaad.",
|
||||
"notFound": "Pagina niet gevonden"
|
||||
},
|
||||
"footer": {
|
||||
"version": "Versie",
|
||||
"copyright": "auteursrechten",
|
||||
"allRightsReserved": "Alle rechten voorbehouden"
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "Dashboard",
|
||||
"servers": "Servers",
|
||||
"documentation": "Documentatie",
|
||||
"credits": "tegoed",
|
||||
"contribute": "Bijdrage leveren",
|
||||
"newServer": "Nieuwe server maken",
|
||||
"navigation": "Navigatie"
|
||||
},
|
||||
"serverWizard": {
|
||||
"newServer": "Nieuwe server maken",
|
||||
"importServer": "Een bestaande server importeren",
|
||||
"importZip": "Importeren uit een zipbestand",
|
||||
"serverName": "Server naam",
|
||||
"serverPath": "Serverpad",
|
||||
"serverType": "Server Type",
|
||||
"selectType": "Selecteer een type",
|
||||
"serverVersion": "Serverversie",
|
||||
"selectVersion": "Selecteer een versie",
|
||||
"absoluteServerPath": "Absoluut pad naar uw server",
|
||||
"serverJar": "Server Jarfile",
|
||||
"minMem": "Minimaal geheugen",
|
||||
"maxMem": "Maximaal geheugen",
|
||||
"serverPort": "Server poort",
|
||||
"defaultPort": "25565 standaard",
|
||||
"sizeInGB": "Grootte in GB",
|
||||
"zipPath": "Serverpad",
|
||||
"absoluteZipPath": "Absoluut pad naar uw server",
|
||||
"resetForm": "Formulier resetten",
|
||||
"importServerButton": "Server importeren!",
|
||||
"buildServer": "Server bouwen!",
|
||||
"quickSettings": "Snelle instellingen",
|
||||
"quickSettingsDescription": "Maak je geen zorgen, je kunt deze later wijzigen",
|
||||
"myNewServer": "My New Server",
|
||||
"bePatient": "Even geduld a.u.b. we ' + (importeren ? 'importeren' : 'downloaden') + ' de server",
|
||||
"importing": "Server importeren...",
|
||||
"downloading": "Server downloaden...",
|
||||
"addRole": "Server toevoegen aan bestaande rollen",
|
||||
"autoCreate": "Als er geen zijn geselecteerd, maakt Crafty er een!",
|
||||
"selectRole": "Selecteer Rollen"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "Dashboard",
|
||||
"memUsage": "Geheugengebruik",
|
||||
"cpuUsage": "CPU gebruik",
|
||||
"host": "Gastheer",
|
||||
"players": "spelers",
|
||||
"backups": "Backups",
|
||||
"newServer": "Nieuwe server maken",
|
||||
"allServers": "Alle servers",
|
||||
"server": "Server",
|
||||
"actions": "Acties",
|
||||
"world": "Wereld",
|
||||
"motd": "MOTD",
|
||||
"version": "Versie",
|
||||
"status": "Toestand",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"lastBackup": "Laatste:",
|
||||
"nextBackup": "Volgende:",
|
||||
"servers": "Servers",
|
||||
"cannotSeeOnMobile": "Zie je niet alles op je GSM?",
|
||||
"cannotSee": "Zie je niet alles?",
|
||||
"cannotSeeOnMobile2": "Probeer de tabel zijwaarts te scrollen.",
|
||||
"max": "Max",
|
||||
"avg": "Avg",
|
||||
"bePatientStart": "Even geduld terwijl we de server starten.<br /> Dit scherm wordt zo vernieuwd",
|
||||
"bePatientStop": "Even geduld terwijl we de server stoppen.<br /> Dit scherm wordt zo vernieuwd",
|
||||
"bePatientRestart": "Even geduld terwijl we de server herstarten.<br /> Dit scherm wordt zo vernieuwd",
|
||||
"bePatientClone": "Even geduld terwijl we de server klonen.<br /> Dit scherm wordt zo vernieuwd",
|
||||
"sendingCommand": "Uw opdracht verzenden",
|
||||
"cpuCurFreq": "CPU huidige kloksnelheid",
|
||||
"cpuMaxFreq": "CPU maximale kloksnelheid",
|
||||
"cpuCores": "CPU Cores",
|
||||
"start": "Begin",
|
||||
"stop": "Stoppen",
|
||||
"clone": "Kloon",
|
||||
"kill": "Proces doden",
|
||||
"restart": "Herstarten",
|
||||
"killing": "Doden proces...",
|
||||
"starting": "Vertraagde start",
|
||||
"delay-explained": "De service/agent is onlangs gestart en vertraagt de start van de minecraft-serverinstantie",
|
||||
"no-servers": "Er zijn momenteel geen servers. Om te beginnen, klik",
|
||||
"welcome": "Welkom bij Crafty Controller "
|
||||
},
|
||||
"accessDenied": {
|
||||
"accessDenied": "Toegang geweigerd",
|
||||
"noAccess": "U heeft geen toegang tot deze bron",
|
||||
"contactAdmin": "Neem contact op met uw serverbeheerder voor toegang tot deze bron, als u denkt dat u toegang zou moeten hebben tot deze bron, neem dan contact op met ondersteuning.",
|
||||
"contact": "Neem contact op met Crafty Control ondersteuning via Discord"
|
||||
},
|
||||
"serverStats": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"serverStatus": "Server Status",
|
||||
"serverStarted": "Server gestart",
|
||||
"serverUptime": "Server Uptime",
|
||||
"players": "Spelers",
|
||||
"memUsage": "Geheugengebruik",
|
||||
"cpuUsage": "CPU gebruik",
|
||||
"version": "Versie",
|
||||
"description": "Beschrijving",
|
||||
"errorCalculatingUptime": "Fout bij berekenen van uptime",
|
||||
"serverTime": "UTC tijd",
|
||||
"unableToConnect": "Niet in staat te verbinden"
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "Server Details",
|
||||
"terminal": "Terminal",
|
||||
"logs": "Logboeken",
|
||||
"schedule": "Schema",
|
||||
"backup": "Backup",
|
||||
"files": "Bestanden",
|
||||
"config": "Configuratie",
|
||||
"playerControls": "Spelersbeheer"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "Automatisch scrollen stoppen",
|
||||
"commandInput": "Voer uw opdracht in",
|
||||
"sendCommand": "Stuur commando",
|
||||
"start": "Begin",
|
||||
"restart": "Herstarten",
|
||||
"stop": "Stoppen",
|
||||
"updating": "Bijwerken...",
|
||||
"starting": "Vertraagde start",
|
||||
"delay-explained": "De service/agent is onlangs gestart en vertraagt de start van de minecraft-serverinstantie"
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "spelers",
|
||||
"bannedPlayers": "Verbannen spelers",
|
||||
"loadingBannedPlayers": "Verbannen spelers laden"
|
||||
},
|
||||
"serverBackups": {
|
||||
"backupNow": "Nu een back-up maken!",
|
||||
"backupAtMidnight": "Automatische back-up maken om middernacht?",
|
||||
"storageLocation": "Opslaglocatie",
|
||||
"storageLocationDesc": "Waar wil je back-ups opslaan?",
|
||||
"maxBackups": "Max Back-ups",
|
||||
"maxBackupsDesc": "Crafty zal niet meer dan N back-ups opslaan, waarbij de oudste wordt verwijderd (voer 0 in om ze allemaal te bewaren)",
|
||||
"save": "Opslaan",
|
||||
"cancel": "Annuleren",
|
||||
"currentBackups": "Huidige back-ups",
|
||||
"download": "Downloaden",
|
||||
"path": "Pad",
|
||||
"size": "Grootte",
|
||||
"delete": "Verwijderen",
|
||||
"backupTask": "Er is een back-uptaak gestart.",
|
||||
"destroyBackup": "Back-up vernietigen \" + file_to_del + \"?",
|
||||
"confirmDelete": "Wil je deze back-up verwijderen? Dit kan niet ongedaan gemaakt worden.",
|
||||
"confirm": "Bevestigen",
|
||||
"options": "Opties"
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "De bestandsbeheerder werkt niet zonder JavaScript",
|
||||
"error": "Fout bij het ophalen van bestanden",
|
||||
"files": "Bestanden",
|
||||
"default": "Standaard",
|
||||
"save": "Opslaan",
|
||||
"editingFile": "Bestand bewerken",
|
||||
"delete": "Verwijderen",
|
||||
"createFile": "Bestand maken",
|
||||
"createDir": "Maak een directory",
|
||||
"rename": "Hernoemen",
|
||||
"createFileQuestion": "Welke naam wil je voor het nieuwe bestand?",
|
||||
"createDirQuestion": "Welke naam wil je voor de nieuwe directory?",
|
||||
"renameItemQuestion": "Wat moet de nieuwe naam worden?",
|
||||
"deleteItemQuestion": "Weet u zeker dat u \" + naam + \" wilt verwijderen?",
|
||||
"deleteItemQuestionMessage": "Je verwijdert \\\"\" + pad + \"\\\"!<br/><br/>Deze actie is onomkeerbaar en het gaat voor altijd verloren!",
|
||||
"yesDelete": "Ja, ik begrijp de gevolgen",
|
||||
"noDelete": "Nee",
|
||||
"unsupportedLanguage": "Waarschuwing: dit is geen ondersteund bestandstype",
|
||||
"keybindings": "Sneltoetsen",
|
||||
"fileReadError": "Bestand leesfout",
|
||||
"upload": "Uploaden",
|
||||
"unzip": "Uitpakken",
|
||||
"clickUpload": "Klik hier om uw bestanden te selecteren",
|
||||
"uploadTitle": "Bestanden uploaden naar: ",
|
||||
"waitUpload": "Even geduld terwijl we uw bestanden uploaden a.u.b.... Dit kan even duren.",
|
||||
"stayHere": "VERLAAT DEZE PAGINA NIET!",
|
||||
"close": "Sluit",
|
||||
"download": "Downloaden"
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "Server naam",
|
||||
"serverNameDesc": "Hoe je deze server wilt noemen",
|
||||
"serverPath": "Werkmap van server",
|
||||
"serverPathDesc": "Absoluut volledig pad (exclusief executable)",
|
||||
"serverLogLocation": "Locatie serverlog",
|
||||
"serverLogLocationDesc": "Absoluut volledig pad naar het logbestand",
|
||||
"serverExecutable": "Executable van de server",
|
||||
"serverExecutableDesc": "Het executable van de server",
|
||||
"serverExecutionCommand": "Serveruitvoeringsopdracht",
|
||||
"serverExecutionCommandDesc": "Wat wordt gelanceerd in een verborgen terminal",
|
||||
"serverStopCommand": "Server Stop Commando",
|
||||
"serverStopCommandDesc": "Commando om het programma te verzenden om te stoppen",
|
||||
"serverAutostartDelay": "Vertraging autostart server",
|
||||
"serverAutostartDelayDesc": "Vertraging voor automatisch starten (indien hieronder ingeschakeld)",
|
||||
"serverIP": "Server IP",
|
||||
"serverIPDesc": "IP Crafty zou verbinding moeten maken voor statistieken (probeer een echt ip in plaats van 127.0.0.1 als je problemen hebt)",
|
||||
"serverPort": "Server poort",
|
||||
"serverPortDesc": "Port Crafty zou verbinding moeten maken voor statistieken",
|
||||
"removeOldLogsAfter": "Oude logboeken verwijderen na",
|
||||
"removeOldLogsAfterDesc": "Hoeveel dagen een logbestand oud moet zijn om te worden verwijderd (0 is uitgeschakeld)",
|
||||
"serverAutoStart": "Server automatisch starten",
|
||||
"serverCrashDetection": "Detectie servercrash",
|
||||
"save": "Sparen",
|
||||
"cancel": "Annuleren",
|
||||
"deleteServer": "Server verwijderen",
|
||||
"stopBeforeDeleting": "Stop de server voordat u dit verwijdert",
|
||||
"exeUpdateURLDesc": "Directe download-URL voor updates.",
|
||||
"exeUpdateURL": "Uitvoerbare server-update-URL",
|
||||
"update": "Uitvoerbaar bestand bijwerken",
|
||||
"bePatientUpdate": "Even geduld terwijl we de server updaten. De downloadtijden kunnen variëren, afhankelijk van je internetsnelheden.<br /> Dit scherm wordt zo vernieuwd",
|
||||
"sendingRequest": "Uw verzoek verzenden...",
|
||||
"deleteServerQuestion": "Server verwijderen?",
|
||||
"deleteServerQuestionMessage": "Weet u zeker dat u deze server wilt verwijderen? Deze actie is onomkeerbaar...",
|
||||
"yesDelete": "Ja, verwijderen",
|
||||
"noDelete": "Nee, ga terug",
|
||||
"deleteFilesQuestion": "Serverbestanden van de machine verwijderen?",
|
||||
"deleteFilesQuestionMessage": "Wil je dat Crafty alle serverbestanden van de hostmachine verwijdert?",
|
||||
"yesDeleteFiles": "Ja, bestanden verwijderen",
|
||||
"noDeleteFiles": "Nee, gewoon uit het paneel verwijderen",
|
||||
"sendingDelete": "Server verwijderen",
|
||||
"bePatientDelete": "Even geduld terwijl we uw server uit het Crafty-paneel verwijderen. Dit scherm wordt over enkele ogenblikken gesloten.",
|
||||
"bePatientDeleteFiles" : "Even geduld terwijl we uw server uit het Crafty-paneel verwijderen, en alle bestanden verwijderen. Dit scherm wordt over enkele ogenblikken gesloten."
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "Serverconfiguratiegebied",
|
||||
"desc": "Hier kunt u de configuratie van uw server wijzigen",
|
||||
"perms": [
|
||||
"Het wordt <code>NIET</code> aanbevolen om de paden van een door Crafty beheerde server te wijzigen.",
|
||||
"Paden wijzigen <code>KAN</code> dingen kapotmaken, vooral op Linux-type besturingssystemen waar bestandspermissies meer vergrendeld zijn.",
|
||||
"<br /><br/>",
|
||||
"Als u denkt dat u de locatie van een server moet wijzigen, kunt u dit doen zolang u de \"crafty\"-gebruiker toestemming geeft om te lezen / schrijven naar het serverpad.",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"Op Linux kunt u dit het beste doen door het volgende uit te voeren:<br />",
|
||||
"<code>",
|
||||
" sudo chown crafty:crafty /path/to/your/server -R<br />",
|
||||
" sudo chmod 2775 /path/to/your/server -R<br />",
|
||||
"</code>"
|
||||
]
|
||||
},
|
||||
"panelConfig": {
|
||||
"save": "Opslaan",
|
||||
"cancel": "Annuleren",
|
||||
"delete": "Verwijderen"
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
"decimal": "",
|
||||
"emptyTable": "Geen data beschikbaar in de tabel",
|
||||
"info": "_START_ tot _END_ van _TOTAL_ inzendingen weergeven",
|
||||
"infoEmpty": "0 tot 0 van 0 items weergeven",
|
||||
"infoFiltered": "(gefilterd uit _MAX_ totale inzendingen)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Toon _MENU_ items",
|
||||
"loadingRecords": "Bezig met laden...",
|
||||
"processing": "Verwerken...",
|
||||
"search": "Zoekopdracht:",
|
||||
"zeroRecords": "Geen overeenkomende records gevonden",
|
||||
"paginate": {
|
||||
"first": "Eerste",
|
||||
"last": "Laatst",
|
||||
"next": "Volgende",
|
||||
"previous": "Vorig"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": activeren om kolom oplopend te sorteren",
|
||||
"sortDescending": ": activeren om kolom aflopend te sorteren"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "Verzameling <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
|
||||
"colvis": "Column Visibility",
|
||||
"colvisRestore": "Zichtbaarheid herstellen",
|
||||
"copy": "Kopiëren",
|
||||
"copyKeys": "Druk op ctrl of u2318 + C om de tabelgegevens naar uw systeemklembord te kopiëren.<br><br>Klik op dit bericht of druk op escape om te annuleren.",
|
||||
"copySuccess": {
|
||||
"1": "1 rij gekopieerd naar klembord",
|
||||
"_": "%d rijen gekopieerd naar klembord"
|
||||
},
|
||||
"copyTitle": "Kopieer naar klembord",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Toon alle rijen",
|
||||
"1": "Toon 1 rij",
|
||||
"_": "%d rijen tonen"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Afdrukken"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"0": "Klik op een rij om deze te selecteren",
|
||||
"1": "%d rij geselecteerd",
|
||||
"_": "%d rijen geselecteerd"
|
||||
},
|
||||
"cells": {
|
||||
"0": "Klik op een cel om deze te selecteren",
|
||||
"1": "%d cel geselecteerd",
|
||||
"_": "%d cellen geselecteerd"
|
||||
},
|
||||
"columns": {
|
||||
"0": "Klik op een kolom om deze te selecteren",
|
||||
"1": "%d kolom geselecteerd",
|
||||
"_": "%d kolommen geselecteerd"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"doesNotWorkWithoutJavascript": "<strong>Waarschuwing: </strong>Crafty werkt niet goed als JavaScript niet is ingeschakeld!"
|
||||
}
|
||||
}
|
350
app/translations/zh_CN.json
Normal file
@ -0,0 +1,350 @@
|
||||
{
|
||||
"login": {
|
||||
"forgotPassword": "忘记密码",
|
||||
"login": "登录",
|
||||
"password": "密码",
|
||||
"username": "用户名"
|
||||
},
|
||||
"error": {
|
||||
"hereIsTheError": "错误如下",
|
||||
"contact": "通过 Discord 联系 Crafty Control 支持",
|
||||
"terribleFailure": "多糟糕的错误!",
|
||||
"embarassing": "哦,天哪,这太尴尬了。",
|
||||
"error": "错误!",
|
||||
"start-error": "服务器 {} 启动失败,错误代码为:{}",
|
||||
"closedPort": "我们检测到端口 {} 在主机上可能没有打开,或者被防火墙阻断了。远程客户端到服务器的连接可能受限。",
|
||||
"internet": "我们检测到运行 Crafty 的设备没有网络连接。客户端到服务器的连接可能受限。",
|
||||
"eulaTitle": "同意最终用户许可协议(EULA)",
|
||||
"eulaMsg": "你必须同意最终用户许可协议(EULA)。一份 Mojang EULA 副本的链接在此消息下方。",
|
||||
"eulaAgree": "你同意吗?",
|
||||
"noJava": "服务器 {} 启动失败,并输出了如下错误码:我们检测到 Java 未安装。请先安装 java 然后再启动服务器。"
|
||||
},
|
||||
"404": {
|
||||
"contact": "通过 Discord 联系 Crafty Control 支持",
|
||||
"unableToFind": "我们无法找到您想要查看的页面。请再试一次,或者返回上一页并刷新。",
|
||||
"notFound": "页面未找到"
|
||||
},
|
||||
"footer": {
|
||||
"version": "版本",
|
||||
"copyright": "Copyright",
|
||||
"allRightsReserved": "All rights reserved"
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "仪表板",
|
||||
"servers": "服务器",
|
||||
"documentation": "文档",
|
||||
"credits": "鸣谢",
|
||||
"contribute": "贡献",
|
||||
"newServer": "创建新服务器",
|
||||
"navigation": "导航栏"
|
||||
},
|
||||
"serverWizard": {
|
||||
"newServer": "创建新服务器",
|
||||
"importServer": "导入现有服务器",
|
||||
"importZip": "从 Zip 文件导入",
|
||||
"serverName": "服务器名称",
|
||||
"serverPath": "服务器路径",
|
||||
"serverType": "服务器类型",
|
||||
"selectType": "选择一种类型",
|
||||
"serverVersion": "服务器版本",
|
||||
"selectVersion": "选择一个版本",
|
||||
"absoluteServerPath": "您的服务器的绝对路径",
|
||||
"serverJar": "服务器 Jar 文件",
|
||||
"minMem": "最小内存",
|
||||
"maxMem": "最大内存",
|
||||
"serverPort": "服务器端口",
|
||||
"defaultPort": "默认值为 25565",
|
||||
"sizeInGB": "大小(以 GB 为单位)",
|
||||
"zipPath": "服务器路径",
|
||||
"absoluteZipPath": "您的服务器的绝对路径",
|
||||
"resetForm": "重置表单",
|
||||
"importServerButton": "导入服务器!",
|
||||
"buildServer": "建立服务器!",
|
||||
"quickSettings": "快捷设置",
|
||||
"quickSettingsDescription": "别担心,你可以稍后再更改这些设置",
|
||||
"myNewServer": "我的新服务器",
|
||||
"bePatient": "请耐心等待,我们正在 ' + (importing ? '导入' : '下载') + ' 服务器",
|
||||
"importing": "导入服务器中……",
|
||||
"downloading": "下载服务器中……",
|
||||
"addRole": "将服务器添加到现有角色",
|
||||
"autoCreate": "如果没有选择任何角色,Crafty 将会为您创建一个!",
|
||||
"selectRole": "选择角色"
|
||||
},
|
||||
"dashboard": {
|
||||
"dashboard": "仪表板",
|
||||
"memUsage": "内存使用率",
|
||||
"cpuUsage": "CPU 使用率",
|
||||
"host": "主机",
|
||||
"players": "玩家",
|
||||
"backups": "备份",
|
||||
"newServer": "创建新服务器",
|
||||
"allServers": "所有服务器",
|
||||
"server": "服务器",
|
||||
"actions": "操作",
|
||||
"world": "世界",
|
||||
"motd": "今日消息(MOTD)",
|
||||
"version": "版本",
|
||||
"status": "状态",
|
||||
"online": "在线",
|
||||
"offline": "离线",
|
||||
"lastBackup": "上次:",
|
||||
"nextBackup": "下次:",
|
||||
"servers": "服务器",
|
||||
"cannotSeeOnMobile": "在移动设备上什么都看不到?",
|
||||
"cannotSee": "什么都看不到?",
|
||||
"cannotSeeOnMobile2": "尝试横向滚动表格。",
|
||||
"max": "最大",
|
||||
"avg": "平均",
|
||||
"bePatientStart": "请耐心等待,我们正在启动服务器。<br /> 稍后此页面会刷新",
|
||||
"bePatientStop": "请耐心等待,我们正在停止服务器。<br /> 稍后此页面会刷新",
|
||||
"bePatientRestart": "请耐心等待,我们正在重启服务器。<br /> 稍后此页面会刷新",
|
||||
"bePatientClone": "请耐心等待,我们正在克隆服务器。<br /> 稍后此页面会刷新",
|
||||
"sendingCommand": "正在发送您的指令",
|
||||
"cpuCurFreq": "当前 CPU 时钟",
|
||||
"cpuMaxFreq": "最大 CPU 时钟",
|
||||
"cpuCores": "CPU 核心",
|
||||
"start": "启动",
|
||||
"stop": "停止",
|
||||
"clone": "克隆",
|
||||
"kill": "杀死进程",
|
||||
"restart": "重启",
|
||||
"killing": "正在杀死进程……",
|
||||
"starting": "延迟启动",
|
||||
"delay-explained": "服务进程已经在刚才启动,并且正在延迟 Minecraft 服务器实例的启动",
|
||||
"no-servers": "当前没有服务器。要开始,请点击",
|
||||
"welcome": "欢迎来到 Crafty Controller"
|
||||
},
|
||||
"accessDenied": {
|
||||
"accessDenied": "拒绝访问",
|
||||
"noAccess": "您没有权限访问此资源",
|
||||
"contactAdmin": "联系您的服务器管理员来获得访问此资源的权限,或者如果您认为您应该有权限访问此资源,请联系支持。",
|
||||
"contact": "通过 Discord 联系 Crafty Control 支持"
|
||||
},
|
||||
"serverStats": {
|
||||
"online": "运行中",
|
||||
"offline": "已停止",
|
||||
"serverStatus": "服务器状态",
|
||||
"serverStarted": "服务器已启动",
|
||||
"serverUptime": "服务器正常运行时间",
|
||||
"players": "玩家",
|
||||
"memUsage": "内存使用率",
|
||||
"cpuUsage": "CPU 使用率",
|
||||
"version": "版本",
|
||||
"description": "简介",
|
||||
"errorCalculatingUptime": "计算正常运行时间时发生错误",
|
||||
"serverTime": "UTC 时间",
|
||||
"unableToConnect": "无法连接"
|
||||
},
|
||||
"serverDetails": {
|
||||
"serverDetails": "服务器详情",
|
||||
"terminal": "终端",
|
||||
"logs": "日志",
|
||||
"schedule": "计划",
|
||||
"backup": "备份",
|
||||
"files": "文件",
|
||||
"config": "配置",
|
||||
"playerControls": "玩家管理"
|
||||
},
|
||||
"serverTerm": {
|
||||
"stopScroll": "停止自动滚动",
|
||||
"commandInput": "输入您的指令",
|
||||
"sendCommand": "发送指令",
|
||||
"start": "启动",
|
||||
"restart": "重启",
|
||||
"stop": "停止",
|
||||
"updating": "更新中……",
|
||||
"starting": "延迟启动",
|
||||
"delay-explained": "服务进程已经在刚才启动,并且正在延迟 Minecraft 服务器实例的启动"
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"players": "玩家",
|
||||
"bannedPlayers": "已封禁的玩家",
|
||||
"loadingBannedPlayers": "正在加载已封禁的玩家"
|
||||
},
|
||||
"serverBackups": {
|
||||
"backupNow": "现在备份!",
|
||||
"backupAtMidnight": "午夜自动备份?",
|
||||
"storageLocation": "存储位置",
|
||||
"storageLocationDesc": "您想要在哪里存储备份?",
|
||||
"maxBackups": "最大备份数量",
|
||||
"maxBackupsDesc": "Crafty 不会存储多于 N 个备份,并且会删除最旧的备份(输入 0 以保留所有备份)",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"currentBackups": "现有备份",
|
||||
"download": "下载",
|
||||
"path": "路径",
|
||||
"size": "大小",
|
||||
"delete": "删除",
|
||||
"backupTask": "一个备份任务已开始。",
|
||||
"destroyBackup": "删除备份 \" + file_to_del + \"?",
|
||||
"confirmDelete": "您想要删除这个备份吗?此操作不能撤销。",
|
||||
"confirm": "确认",
|
||||
"options": "选项",
|
||||
"restoring": "正在恢复备份。这需要一点时间。请耐心等待。",
|
||||
"restore": "恢复",
|
||||
"confirmRestore": "你确定要从此备份恢复吗?所有现存的服务器文件将更改到备份时的状态,并且无法撤销。"
|
||||
},
|
||||
"serverFiles": {
|
||||
"noscript": "文件管理器无法在没有 JavaScript 的情况下使用",
|
||||
"error": "获取文件时发生错误",
|
||||
"files": "文件",
|
||||
"default": "默认",
|
||||
"save": "保存",
|
||||
"editingFile": "正在编辑文件",
|
||||
"delete": "删除",
|
||||
"createFile": "创建文件",
|
||||
"createDir": "创建目录",
|
||||
"rename": "重命名",
|
||||
"createFileQuestion": "您希望新文件叫什么名字?",
|
||||
"createDirQuestion": "您希望新目录叫什么名字?",
|
||||
"renameItemQuestion": "新名称应当是什么?",
|
||||
"deleteItemQuestion": "您确定要删除 \" + name + \" 吗?",
|
||||
"deleteItemQuestionMessage": "您正在删除 \\\"\" + path + \"\\\"!<br/><br/>此操作不可逆转,文件将永远遗失!",
|
||||
"yesDelete": "是,我知道结果",
|
||||
"noDelete": "否",
|
||||
"unsupportedLanguage": "警告:这不是一个受支持的文件类型",
|
||||
"keybindings": "按键绑定",
|
||||
"fileReadError": "文件读取错误",
|
||||
"upload": "上传",
|
||||
"unzip": "解压",
|
||||
"clickUpload": "点击这里来选择您的文件",
|
||||
"uploadTitle": "上传文件到:",
|
||||
"waitUpload": "请等待,我们正在上传您的文件……这需要一点时间。",
|
||||
"stayHere": "请不要离开此页面!",
|
||||
"close": "关闭",
|
||||
"download": "下载"
|
||||
},
|
||||
"serverConfig": {
|
||||
"serverName": "服务器名称",
|
||||
"serverNameDesc": "您希望把这个服务器叫做什么",
|
||||
"serverPath": "服务器运行目录",
|
||||
"serverPathDesc": "完整绝对路径(不包含可执行文件)",
|
||||
"serverLogLocation": "服务器日志路径",
|
||||
"serverLogLocationDesc": "日志文件的完整绝对路径",
|
||||
"serverExecutable": "服务器可执行文件",
|
||||
"serverExecutableDesc": "服务器的可执行文件",
|
||||
"serverExecutionCommand": "服务器运行命令",
|
||||
"serverExecutionCommandDesc": "在隐藏的终端内要如何启动服务器",
|
||||
"serverStopCommand": "服务器停止指令",
|
||||
"serverStopCommandDesc": "要发送给程序以关闭它的指令",
|
||||
"serverAutostartDelay": "服务器自动启动延迟",
|
||||
"serverAutostartDelayDesc": "自动启动前的延迟(如果已在下方启用)",
|
||||
"serverIP": "服务器 IP",
|
||||
"serverIPDesc": "Crafty 要连接以获取状态的 IP(如果遇到问题,尝试使用真实 IP 而非 127.0.0.1)",
|
||||
"serverPort": "服务器端口",
|
||||
"serverPortDesc": "Crafty 要连接以获取状态的端口",
|
||||
"removeOldLogsAfter": "此时间后删除旧日志",
|
||||
"removeOldLogsAfterDesc": "日志文件要在多少天后视为旧文件被删除(0 为关闭)",
|
||||
"serverAutoStart": "服务器自动启动",
|
||||
"serverCrashDetection": "服务器崩溃检测",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"deleteServer": "删除服务器",
|
||||
"stopBeforeDeleting": "请在删除之前停止服务器",
|
||||
"exeUpdateURLDesc": "用于下载更新的直接链接。",
|
||||
"exeUpdateURL": "服务器可执行文件更新地址",
|
||||
"update": "更新可执行文件",
|
||||
"bePatientUpdate": "请耐心等待,我们正在更新服务器。下载时长可能因您的网络速度而异。<br /> 稍后此页面会刷新",
|
||||
"sendingRequest": "正在发送您的请求……",
|
||||
"deleteServerQuestion": "删除服务器?",
|
||||
"deleteServerQuestionMessage": "您确定要删除此服务器吗?在此之后将无法撤销……",
|
||||
"yesDelete": "是,删除",
|
||||
"noDelete": "否,返回",
|
||||
"deleteFilesQuestion": "从设备上删除服务器文件?",
|
||||
"deleteFilesQuestionMessage": "您想要 Crafty 从主机上删除所有的服务器文件吗?",
|
||||
"yesDeleteFiles": "是,删除文件",
|
||||
"noDeleteFiles": "否,只从面板中移除",
|
||||
"sendingDelete": "正在删除服务器",
|
||||
"bePatientDelete": "请耐心等待,我们正在从 Crafty 面板中移除服务器。稍后此页面会关闭。",
|
||||
"bePatientDeleteFiles" : "请耐心等待,我们正在从 Crafty 面板中移除服务器并删除所有文件。稍后此页面会关闭。"
|
||||
},
|
||||
"serverConfigHelp": {
|
||||
"title": "服务器配置区",
|
||||
"desc": "您可以在这里更改您的服务器配置",
|
||||
"perms": [
|
||||
"我们<code>不推荐</code>更改由 Crafty 管理的服务器的路径。",
|
||||
"更改路径<code>可能会</code>破坏一些东西,尤其是在 Linux 这类文件权限锁定得更加严格的操作系统上。",
|
||||
"<br /><br/>",
|
||||
"如果您认为您必须更改服务器存放的位置,你可能需要给予 \"crafty\" 用户对服务器路径的读取/写入权限。",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"在 Linux 上,最好通过执行如下命令来完成:<br />",
|
||||
"<code>",
|
||||
" sudo chown crafty:crafty /您的/服务器/路径 -R<br />",
|
||||
" sudo chmod 2775 /您的/服务器/路径 -R<br />",
|
||||
"</code>"
|
||||
]
|
||||
},
|
||||
"panelConfig": {
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"delete": "删除"
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
"decimal": "",
|
||||
"emptyTable": "数据表中没有可用的数据",
|
||||
"info": "正在显示从 _START_ 到 _END_ 的共 _TOTAL_ 个项目",
|
||||
"infoEmpty": "正在显示从 0 到 0 的共 0 个项目",
|
||||
"infoFiltered": "(从 _MAX_ 个项目中筛选出)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "显示 _MENU_ 个项目",
|
||||
"loadingRecords": "正在加载……",
|
||||
"processing": "正在处理……",
|
||||
"search": "搜索:",
|
||||
"zeroRecords": "没有找到匹配的记录",
|
||||
"paginate": {
|
||||
"first": "首页",
|
||||
"last": "末页",
|
||||
"next": "下一页",
|
||||
"previous": "上一页"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ":激活对队列的升序排列",
|
||||
"sortDescending": ":激活对队列的降序排列"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "合集 <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
|
||||
"colvis": "列可见性",
|
||||
"colvisRestore": "恢复可见性",
|
||||
"copy": "复制",
|
||||
"copyKeys": "按 ctrl 或 u2318 + C 以复制表中的数据到您的系统剪贴板。<br><br>点击这条消息或者按 escape(ESC)来取消。",
|
||||
"copySuccess": {
|
||||
"1": "复制了 1 行到剪贴板",
|
||||
"_": "复制了 %d 行到剪贴板"
|
||||
},
|
||||
"copyTitle": "复制到剪贴板",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "显示所有行",
|
||||
"1": "显示 1 行",
|
||||
"_": "显示 %d 行"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "打印"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"0": "点击某一行以选择",
|
||||
"1": "%d 行已选中",
|
||||
"_": "%d 行已选中"
|
||||
},
|
||||
"cells": {
|
||||
"0": "点击某个单元格以选择",
|
||||
"1": "%d 个单元格已选中",
|
||||
"_": "%d 个单元格已选中"
|
||||
},
|
||||
"columns": {
|
||||
"0": "点击某一列以选择",
|
||||
"1": "%d 列已选中",
|
||||
"_": "%d 列已选中"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"doesNotWorkWithoutJavascript": "<strong>警告:</strong>Crafty 无法在没有 JavaScript 的情况下使用!"
|
||||
}
|
||||
}
|
37
config_examples/nginx.conf.example
Normal file
@ -0,0 +1,37 @@
|
||||
# Config based on https://gitlab.com/lewishill211/crafty-controller-https
|
||||
# Edits for 4.0 compatibility by pretzelDewey - https://gitlab.com/amcmanu3
|
||||
|
||||
upstream crafty {
|
||||
server "<DOMAIN>";
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
rewrite ^(.*) https://$host$1 permanent;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name <DOMAIN>;
|
||||
ssl_certificate <CERIFICATE_LOCATION>;
|
||||
ssl_certificate_key <KEYFILE_LOCATION>;
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass https://localhost:8443;
|
||||
|
||||
proxy_buffering off;
|
||||
client_max_body_size 0;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
send_timeout 3600s;
|
||||
}
|
||||
}
|
97
docker/unraid.xml
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0"?>
|
||||
<Container version="2">
|
||||
<Name>Crafty</Name>
|
||||
<Repository>crafty-controller/crafty-commander:latest</Repository>
|
||||
<Registry>registry.gitlab.com/crafty-controller/crafty-commander</Registry>
|
||||
<Network>bridge</Network>
|
||||
<MyIP/>
|
||||
<Shell>sh</Shell>
|
||||
<Privileged>false</Privileged>
|
||||
<Support>https://discord.gg/9VJPhCE</Support>
|
||||
<Project>https://craftycontrol.com/</Project>
|
||||
<Overview>Crafty controller is a lightweight minecraft wrapper with great featuers and a awesome gui, acessible from a modern web browser.
|
||||
The username and password appear on the first launch in the docker logs!
|
||||
Crafty 4 is the successor of crafty controller, the docker image is no longer maintained on dockerhub. (now on gitlab)
|
||||
For official support join the discord: https://discord.gg/9VJPhCE
|
||||
For migration from 3.x please refer to the documentation on gitlab: htps://gitlab.com/crafty-docs
|
||||
</Overview>
|
||||
<Category>GameServers: Other:</Category>
|
||||
<WebUI>https://[IP]:[PORT:8443]/</WebUI>
|
||||
<TemplateURL>https://raw.githubusercontent.com/mrFred-1a4/Freddy0-Crafty-Controller/main/Crafty-controller-freddy0.xml</TemplateURL>
|
||||
<Icon>https://gitlab.com/crafty-controller/crafty-commander/-/raw/master/app/frontend/static/assets/images/logo_square.jpg</Icon>
|
||||
<ExtraParams/>
|
||||
<PostArgs/>
|
||||
<CPUset/>
|
||||
<DateInstalled>1641595780</DateInstalled>
|
||||
<DonateText>Help to support Crafty on Patreon</DonateText>
|
||||
<DonateLink>https://www.patreon.com/craftycontroller</DonateLink>
|
||||
<Description>Crafty controller is a lightweight minecraft wrapper with great featuers and a awesome gui, acessible from a modern web browser.
|
||||
The username and password appear on the first launch in the docker logs!
|
||||
Crafty 4 is the successor of crafty controller, the docker image is no longer maintained on dockerhub. (now on gitlab)
|
||||
For official support join the discord: https://discord.gg/9VJPhCE
|
||||
For migration from 3.x please refer to the documentation on gitlab: https://gitlab.com/crafty-controller/crafty-commander/-/wikis/home
|
||||
</Description>
|
||||
<Networking>
|
||||
<Mode>bridge</Mode>
|
||||
<Publish>
|
||||
<Port>
|
||||
<HostPort>8443</HostPort>
|
||||
<ContainerPort>8443</ContainerPort>
|
||||
<Protocol>tcp</Protocol>
|
||||
</Port>
|
||||
<Port>
|
||||
<HostPort>8000</HostPort>
|
||||
<ContainerPort>8000</ContainerPort>
|
||||
<Protocol>tcp</Protocol>
|
||||
</Port>
|
||||
<Port>
|
||||
<HostPort>25500-25600</HostPort>
|
||||
<ContainerPort>25500-25600</ContainerPort>
|
||||
<Protocol>tcp</Protocol>
|
||||
</Port>
|
||||
<Port>
|
||||
<HostPort>8123</HostPort>
|
||||
<ContainerPort>8123</ContainerPort>
|
||||
<Protocol>tcp</Protocol>
|
||||
</Port>
|
||||
<Port>
|
||||
<HostPort>19132</HostPort>
|
||||
<ContainerPort>19132</ContainerPort>
|
||||
<Protocol>udp</Protocol>
|
||||
</Port>
|
||||
</Publish>
|
||||
</Networking>
|
||||
<Data>
|
||||
<Volume>
|
||||
<HostDir>/mnt/user/appdata/Crafty-4/servers/</HostDir>
|
||||
<ContainerDir>/commander/servers</ContainerDir>
|
||||
<Mode>rw</Mode>
|
||||
</Volume>
|
||||
<Volume>
|
||||
<HostDir>/mnt/user/appdata/Crafty-4/backups/</HostDir>
|
||||
<ContainerDir>/commander/backups</ContainerDir>
|
||||
<Mode>rw</Mode>
|
||||
</Volume>
|
||||
<Volume>
|
||||
<HostDir>/mnt/user/appdata/Crafty-4/logs/</HostDir>
|
||||
<ContainerDir>/commander/logs</ContainerDir>
|
||||
<Mode>rw</Mode>
|
||||
</Volume>
|
||||
<Volume>
|
||||
<HostDir>/mnt/user/appdata/Crafty-4/config/</HostDir>
|
||||
<ContainerDir>/commander/app/config</ContainerDir>
|
||||
<Mode>rw</Mode>
|
||||
</Volume>
|
||||
</Data>
|
||||
<Environment/>
|
||||
<Labels/>
|
||||
<Config Name="Port for https traffic" Target="8443" Default="8443" Mode="tcp" Description="Port for https traffic to the web Ui" Type="Port" Display="always-hide" Required="true" Mask="false">8443</Config>
|
||||
<Config Name="Port for http redirections" Target="8000" Default="1800" Mode="tcp" Description="http traffic will automatically be redirected to htps" Type="Port" Display="always-hide" Required="true" Mask="false">8000</Config>
|
||||
<Config Name="Minecraft ports" Target="25500-25600" Default="25500-25600" Mode="tcp" Description="Container Port: 25500-25600 yes, 100 ports for 100 possible Servers" Type="Port" Display="always-hide" Required="true" Mask="false">25500-25600</Config>
|
||||
<Config Name="Port for dynmap" Target="8123" Default="8123" Mode="tcp" Description="Dynmap port" Type="Port" Display="always-hide" Required="true" Mask="false">8123</Config>
|
||||
<Config Name="Port for bedrock server" Target="19132" Default="19132" Mode="udp" Description="Bedrock server port" Type="Port" Display="always-hide" Required="true" Mask="false">19132</Config>
|
||||
<Config Name="Server files" Target="/commander/servers" Default="/mnt/user/appdata/Crafty-4/servers/" Mode="rw" Description="Path to the minecraft server folders" Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/servers/</Config>
|
||||
<Config Name="Backup files" Target="/commander/backups" Default="/mnt/user/appdata/Crafty-4/backups/" Mode="rw" Description="Server Backups" Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/backups/</Config>
|
||||
<Config Name="Server Logs" Target="/commander/logs" Default="/mnt/user/appdata/Crafty-4/logs/" Mode="rw" Description="Logs" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/logs/</Config>
|
||||
<Config Name="Crafty Configuration" Target="/commander/app/config" Default="/mnt/user/appdata/Crafty-4/config/" Mode="rw" Description="Path to the persistent Crafty files" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/config/</Config>
|
||||
</Container>
|
51
main.py
@ -1,3 +1,4 @@
|
||||
from cmd import Cmd
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
@ -5,6 +6,8 @@ import time
|
||||
import argparse
|
||||
import logging.config
|
||||
import signal
|
||||
import threading
|
||||
from app.classes.controllers.management_controller import Management_Controller
|
||||
|
||||
""" Our custom classes / pip packages """
|
||||
from app.classes.shared.console import console
|
||||
@ -23,14 +26,14 @@ def do_intro():
|
||||
|
||||
version = helper.get_version_string()
|
||||
|
||||
intro = """
|
||||
{lines}
|
||||
#\t\tWelcome to Crafty Controller - v.{version}\t\t #
|
||||
{lines}
|
||||
# \tServer Manager / Web Portal for your Minecraft server \t #
|
||||
# \t\tHomepage: www.craftycontrol.com\t\t\t #
|
||||
{lines}
|
||||
""".format(lines="/" * 75, version=version)
|
||||
intro = f"""
|
||||
{'/' * 75}
|
||||
#{("Welcome to Crafty Controller - v." + version).center(73, " ")}#
|
||||
{'/' * 75}
|
||||
#{"Server Manager / Web Portal for your Minecraft server".center(73, " ")}#
|
||||
#{"Homepage: www.craftycontrol.com".center(73, " ")}#
|
||||
{'/' * 75}
|
||||
"""
|
||||
|
||||
console.magenta(intro)
|
||||
|
||||
@ -78,13 +81,21 @@ if __name__ == '__main__':
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if helper.check_file_exists('/.dockerenv'):
|
||||
console.cyan("Docker environment detected!")
|
||||
else:
|
||||
if helper.checkRoot():
|
||||
console.critical("Root detected. Root/Admin access denied. Run Crafty again with non-elevated permissions.")
|
||||
time.sleep(5)
|
||||
console.critical("Crafty shutting down. Root/Admin access denied.")
|
||||
sys.exit(0)
|
||||
helper.ensure_logging_setup()
|
||||
|
||||
setup_logging(debug=args.verbose)
|
||||
|
||||
# setting up the logger object
|
||||
logger = logging.getLogger(__name__)
|
||||
print("Logging set to: {} ".format(logger.level))
|
||||
console.cyan("Logging set to: {} ".format(logger.level))
|
||||
|
||||
# print our pretty start message
|
||||
do_intro()
|
||||
@ -101,6 +112,7 @@ if __name__ == '__main__':
|
||||
|
||||
if fresh_install:
|
||||
console.debug("Fresh install detected")
|
||||
console.warning("We have detected a fresh install. Please be sure to forward Crafty's port, {}, through your router/firewall if you would like to be able to access Crafty remotely.".format(helper.get_setting('https_port')))
|
||||
installer.default_settings()
|
||||
else:
|
||||
console.debug("Existing install detected")
|
||||
@ -128,23 +140,25 @@ if __name__ == '__main__':
|
||||
# refresh our cache and schedule for every 12 hoursour cache refresh for serverjars.com
|
||||
tasks_manager.serverjar_cache_refresher()
|
||||
|
||||
# this should always be last
|
||||
tasks_manager.start_main_kill_switch_watcher()
|
||||
|
||||
logger.info("Checking Internet/Port Service. This may take a minute.")
|
||||
console.info("Checking Internet/Port Service. This may take a minute.")
|
||||
logger.info("Checking Internet. This may take a minute.")
|
||||
console.info("Checking Internet. This may take a minute.")
|
||||
|
||||
if not helper.check_internet():
|
||||
console.error("We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.")
|
||||
elif not helper.check_port(helper.get_setting('https_port')):
|
||||
console.error("We have detected Crafty's port, {} may not be open on the host network or a firewall is blocking it. Remote client connections to Crafty may be limited.".format(helper.get_setting('https_port')))
|
||||
console.warning("We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.")
|
||||
|
||||
if not controller.check_system_user():
|
||||
controller.add_system_user()
|
||||
|
||||
Crafty = MainPrompt(tasks_manager, migration_manager)
|
||||
|
||||
project_root = os.path.dirname(__file__)
|
||||
controller.set_project_root(project_root)
|
||||
|
||||
def sigterm_handler(signum, current_stack_frame):
|
||||
print() # for newline
|
||||
logger.info("Recieved SIGTERM, stopping Crafty")
|
||||
console.info("Recieved SIGTERM, stopping Crafty")
|
||||
tasks_manager._main_graceful_exit()
|
||||
Crafty.universal_exit()
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm_handler)
|
||||
@ -156,6 +170,7 @@ if __name__ == '__main__':
|
||||
print() # for newline
|
||||
logger.info("Recieved SIGINT, stopping Crafty")
|
||||
console.info("Recieved SIGINT, stopping Crafty")
|
||||
tasks_manager._main_graceful_exit()
|
||||
Crafty.universal_exit()
|
||||
else:
|
||||
print("Crafty started in daemon mode, no shell will be printed")
|
||||
@ -168,5 +183,5 @@ if __name__ == '__main__':
|
||||
logger.info("Recieved SIGINT, stopping Crafty")
|
||||
console.info("Recieved SIGINT, stopping Crafty")
|
||||
break
|
||||
|
||||
tasks_manager._main_graceful_exit()
|
||||
Crafty.universal_exit()
|
||||
|
@ -1,10 +1,18 @@
|
||||
cryptography~=3.4
|
||||
argon2-cffi~=20.1
|
||||
bleach~=3.1
|
||||
colorama~=0.4
|
||||
cryptography~=3.4
|
||||
libgravatar~=1.0.0
|
||||
peewee~=3.13
|
||||
pexpect~=4.8
|
||||
psutil~=5.7
|
||||
pyOpenSSL~=19.1.0
|
||||
PyYAML==5.3.1
|
||||
requests~=2.26
|
||||
schedules~=1.3
|
||||
termcolor~=1.1
|
||||
tornado~=6.0
|
||||
cached_property==1.5.2
|
||||
apscheduler~=3.8.1
|
||||
cron-validator~=1.0.3
|
||||
tzlocal~=4.1
|