Merge branch 'dev-Silversthorn' into dev-Silversthorn-MVC

This commit is contained in:
Silversthorn 2021-09-21 21:12:56 +02:00
commit 186dcdc616
19 changed files with 419 additions and 42 deletions

87
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,87 @@
stages:
- buildx
- build-dev
- build-prod
buildx:
image: docker:git
services:
- docker:dind
stage: buildx
rules:
- if: $CI_COMMIT_BRANCH == 'dev'
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
GIT_STRATEGY: none
tags:
- docker
artifacts:
paths:
- buildx
expire_in: 1 hour
script:
- export DOCKER_BUILDKIT=1
- git clone git://github.com/docker/buildx ./docker-buildx
- docker build --platform=local -o . ./docker-buildx
docker-build-dev:
image: docker:latest
services:
- name: docker:dind
command: ["--experimental"]
stage: build-dev
tags:
- docker
rules:
- if: $CI_COMMIT_BRANCH == 'dev'
environment:
name: development
before_script:
- mkdir -p ~/.docker/cli-plugins
- mv buildx ~/.docker/cli-plugins/docker-buildx
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- 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
- docker buildx build
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
--build-arg BUILDKIT_INLINE_CACHE=1
--tag "$CI_REGISTRY_IMAGE${tag}"
--platform linux/arm64/v8,linux/amd64
--push .
after_script:
- docker buildx imagetools inspect "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
docker-build-prod:
image: docker:latest
services:
- name: docker:dind
command: ["--experimental"]
stage: build-prod
tags:
- docker
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
environment:
name: production
before_script:
- mkdir -p ~/.docker/cli-plugins
- mv buildx ~/.docker/cli-plugins/docker-buildx
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- 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
- docker buildx build
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
--build-arg BUILDKIT_INLINE_CACHE=1
--tag "$CI_REGISTRY_IMAGE${tag}"
--platform linux/arm64/v8,linux/amd64
--push .
after_script:
- docker buildx imagetools inspect "$CI_REGISTRY_IMAGE${tag}"

View File

@ -19,15 +19,57 @@ Git Repository - https://gitlab.com/crafty-controller/crafty-web
## Basic Docker Usage
A Docker image pipeline is still to be implimented but for example you can expect the image to be located: `crafty/cc-dashboard` and you would change the image in the below `docker run` to this image.
**To get started with docker**, all you need to do is pull the image from this git repository's registry.
This is done by using `docker-compose` or `docker run`(You don't need to clone the Repository and build, like in 3.x ).
If you are building from the `docker-compose` you can find it in `./docker/docker-compose.yml` just `cd` to the docker directory and `docker-compose up -d`
If you have a config folder already from previous local installation or docker setup, the image should mount this volume, if none is present then it will populate its own config folder for you.
If you'd rather not use `docker-compose` you can use the following `docker run`:
### Using the registry image:
The provided image supports both `arm64` and `amd64` out the box, if you have issues though you can build it yourself.
The image is located at: `registry.gitlab.com/crafty-controller/crafty-commander:latest`
| Branch | Status |
| ----------------- | ------------------------------------------------------------------ |
| :latest | [![pipeline status](https://gitlab.com/crafty-controller/crafty-commander/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-commander/-/commits/master) |
| :dev | [![pipeline status](https://gitlab.com/crafty-controller/crafty-commander/badges/dev/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-commander/-/commits/dev) |
While the repository is still **private / pre-release**,
Before you can pull the image you must authenticate docker with the Container Registry.
To authenticate you will need a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
with the minimum scope:
- For read (*pull*) access, `read_registry`.
- For write (*push*) access, `write_registry`.
When you have this just run:
```bash
$ docker login registry.gitlab.com -u <username> -p <token>
```
$ docker build . -t cc-dashboard
# REMEMBER, Build your image!
Then use one of the following methods:
#### docker-compose.yml
```yml
version: '3'
services:
crafty:
container_name: crafty_commander
image: registry.gitlab.com/crafty-controller/crafty-commander:latest
ports:
- "8000:8000" # HTTP
- "8443:8443" # HTTPS
- "8123:8123" # DYNMAP
- "19132:19132/udp" # BEDROCK
- "24000-25600:24000-25600" # MC SERV PORT RANGE
volumes:
- ./docker/backups:/commander/backups
- ./docker/logs:/commander/logs
- ./docker/servers:/commander/servers
- ./docker/config:/commander/app/config
```
#### docker run
```sh
$ docker run \
--name crafty_commander \
-p 8000:8000 \
@ -39,8 +81,29 @@ $ docker run \
-v "/$(pwd)/docker/logs:/commander/logs" \
-v "/$(pwd)/docker/servers:/commander/servers" \
-v "/$(pwd)/docker/config:/commander/app/config" \
cc-dashboard
registry.gitlab.com/crafty-controller/crafty-commander:latest
```
### Building from the cloned repository:
If you are building from `docker-compose` you can find the compose file in `./docker/docker-compose.yml` just `cd` to the docker directory and `docker-compose up -d`
If you'd rather not use `docker-compose` you can use the following `docker run`in the directory where the *Dockerfile* is:
```sh
# REMEMBER, Build your image first!
$ docker build . -t crafty
$ docker run \
--name crafty_commander \
-p 8000:8000 \
-p 8443:8443 \
-p 8123:8123 \
-p 19132:19132/udp \
-p 24000-25600:24000-25600 \
-v "/$(pwd)/docker/backups:/commander/backups" \
-v "/$(pwd)/docker/logs:/commander/logs" \
-v "/$(pwd)/docker/servers:/commander/servers" \
-v "/$(pwd)/docker/config:/commander/app/config" \
crafty
```
A fresh build will take several minutes depending on your system, but will be rapid there after.
If you have a config folder already from previous local installation or docker setup, the image should mount this volume, if none is present then it will populate its own config folder for you.

View File

@ -175,8 +175,8 @@ class Stats:
# TODO: search server properties file for possible override of 127.0.0.1
internal_ip = server_data.get('server_ip', "127.0.0.1")
server_port = server_settings.get('server-port', "25565")
internal_ip = server['server_ip']
server_port = server['server_port']
logger.debug("Pinging {} on port {}".format(internal_ip, server_port))
int_mc_ping = ping(internal_ip, int(server_port))
@ -201,6 +201,8 @@ class Stats:
for s in servers:
server_id = s.get('server_id', None)
server = db_helper.get_server_data_by_id(server_id)
logger.debug('Getting stats for server: {}'.format(server_id))
@ -208,7 +210,7 @@ class Stats:
server_obj = s.get('server_obj', None)
server_obj.reload_server_settings()
server_settings = s.get('server_settings', {})
server_data = s.get('server_data_obj', {})
server_data = self.controller.get_server_data(server_id)
# world data
world_name = server_settings.get('level-name', 'Unknown')
@ -218,8 +220,8 @@ class Stats:
p_stats = self._get_process_stats(server_obj.PID)
# TODO: search server properties file for possible override of 127.0.0.1
internal_ip = server_data.get('server_ip', "127.0.0.1")
server_port = server_settings.get('server-port', "25565")
internal_ip = server['server_ip']
server_port = server['server_port']
logger.debug("Pinging server '{}' on {}:{}".format(s.get('server_name', "ID#{}".format(server_id)), internal_ip, server_port))
int_mc_ping = ping(internal_ip, int(server_port))
@ -276,8 +278,8 @@ class Stats:
p_stats = self._get_process_stats(server_obj.PID)
# TODO: search server properties file for possible override of 127.0.0.1
internal_ip = server_data.get('server_ip', "127.0.0.1")
server_port = server_settings.get('server-port', "25565")
internal_ip = server['server_ip']
server_port = server['server_port']
logger.debug("Pinging server '{}' on {}:{}".format(server.name, internal_ip, server_port))
int_mc_ping = ping(internal_ip, int(server_port))

View File

@ -14,6 +14,7 @@ import html
import zipfile
import pathlib
import shutil
from requests import get
from datetime import datetime
from socket import gethostname
@ -75,6 +76,25 @@ class Helpers:
logger.error("{} does not exist".format(file))
return True
@staticmethod
def check_internet():
try:
requests.get('https://google.com', timeout=1)
return True
except Exception as err:
return False
@staticmethod
def check_port(server_port):
host_public = get('https://api.ipify.org').text
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex((host_public ,server_port))
sock.close()
if result == 0:
return True
else:
return False
def get_setting(self, key, default_return=False):
try:

View File

@ -100,6 +100,14 @@ class Controller:
# add this temp object to the list of init servers
self.servers_list.append(temp_server_dict)
if s['auto_start']:
db_helper.set_waiting_start(s['server_id'], True)
db_helper.get_waiting_start(s['server_id'])
server_stats = db_helper.get_all_servers_stats()
self.refresh_server_settings(s['server_id'])
console.info("Loaded Server: ID {} | Name: {} | Autostart: {} | Delay: {} ".format(
s['server_id'],
s['server_name'],

View File

@ -21,6 +21,7 @@ from app.classes.shared.console import console
from app.classes.models.servers import 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
logger = logging.getLogger(__name__)
@ -183,19 +184,31 @@ class Server:
logger.info("Linux Detected")
logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command))
db_helper.set_waiting_start(self.server_id, False)
try:
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding='utf-8')
except Exception as ex:
msg = "Server {} failed to start with error code: {}".format(self.name, ex)
logger.error(msg)
websocket_helper.broadcast('send_start_error', {
'error': msg
'error': translation.translate('error', 'start-error').format(self.name, ex)
})
return False
if helper.check_internet():
loc_server_port = db_helper.get_server_stats_by_id(self.server_id)['server_port']
if helper.check_port(loc_server_port):
websocket_helper.broadcast('send_start_reload', {
})
else:
websocket_helper.broadcast('send_start_error', {
'error': translation.translate('error', 'closedPort').format(loc_server_port)
})
else:
websocket_helper.broadcast('send_start_error', {
'error': translation.translate('error', 'internet')
})
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding='utf-8')
out_buf = ServerOutBuf(self.process, self.server_id)
logger.debug('Starting virtual terminal listener for server {}'.format(self.name))
@ -351,6 +364,9 @@ class Server:
else:
return False
def get_pid(self):
return self.PID
def detect_crash(self):
logger.info("Detecting possible crash for server: {} ".format(self.name))

View File

@ -2,6 +2,7 @@ import json
import logging
import tempfile
import threading
from typing import Container
import zipfile
import tornado.web
@ -153,6 +154,8 @@ class AjaxHandler(BaseHandler):
if srv_obj.check_running():
srv_obj.send_command(command)
db_helper.add_to_audit_log(user_data['user_id'], "Sent command to {} terminal: {}".format(db_helper.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)
file_name = self.get_body_argument('file_name', default=None, strip=True)
@ -196,6 +199,18 @@ class AjaxHandler(BaseHandler):
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
return
elif page == "kill":
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: {}".format(e))
return
@tornado.web.authenticated
def delete(self, page):
if page == "del_file":

View File

@ -74,6 +74,7 @@ class PanelHandler(BaseHandler):
'error': error,
'time': formatted_time
}
page_data['super_user'] = exec_user['superuser']
# if no servers defined, let's go to the build server area
if page_data['server_stats']['total'] == 0 and page != "error" and page != "credits" and page != "contribute":
@ -127,11 +128,18 @@ class PanelHandler(BaseHandler):
elif page == 'dashboard':
if exec_user['superuser'] == 1:
page_data['servers'] = self.controller.servers.get_all_servers_stats()
for data in page_data['servers']:
data['stats']['waiting_start'] = db_helper.get_waiting_start(int(data['stats']['server_id']['server_id']))
else:
user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id)
logger.debug("ASFR: {}".format(user_auth))
page_data['servers'] = user_auth
total_players = 0
for server in db_helper.get_all_defined_servers():
total_players += len(self.controller.stats.get_server_players(server['server_id']))
page_data['num_players'] = total_players
for s in page_data['servers']:
try:
data = json.loads(s['int_ping_results'])
@ -171,6 +179,7 @@ class PanelHandler(BaseHandler):
# server_data isn't needed since the server_stats also pulls server data
page_data['server_data'] = self.controller.servers.get_server_data_by_id(server_id)
page_data['server_stats'] = self.controller.servers.get_server_stats_by_id(server_id)
page_data['waiting_start'] = self.controller.servers.get_waiting_start(server_id)
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
page_data['active_link'] = subpage
page_data['permissions'] = {
@ -637,7 +646,10 @@ class PanelHandler(BaseHandler):
server_id = self.get_argument('id', None)
backup_path = bleach.clean(self.get_argument('backup_path', None))
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")
@ -652,10 +664,16 @@ class PanelHandler(BaseHandler):
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)
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)
self.controller.management.add_to_audit_log(exec_user['user_id'],
"Edited server {}: updated backups".format(server_id),

View File

@ -228,7 +228,12 @@ if (webSocket) {
var x = document.querySelector('.modal-backdrop');
if(x){
x.remove()}
bootbox.alert(start_error.error);
bootbox.alert({
message: start_error.error,
callback: function () {
location.reload();
}
})
});
}

View File

@ -28,7 +28,9 @@
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
{% end %}
</div>
{% 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 %}
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>Sign Out</a>
</div>
</li>

View File

@ -60,7 +60,7 @@
<div class="d-flex">
<div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players') }}</h5>
<h3 class="mb-0 font-weight-semibold">18</h3>
<h3 class="mb-0 font-weight-semibold">{{ data['num_players'] }}</h3>
</div>
<div class="wrapper my-auto ml-auto ml-lg-4">
@ -126,13 +126,17 @@
<td id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% if server['user_command_permission'] %}
{% if server['stats']['running'] %}
<a class="stop_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-stop"></i></a> &nbsp;
<a class="restart_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-sync"></i></a> &nbsp;
<a class="stop_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'stop') }}> <i class="fas fa-stop"></i></a> &nbsp;
<a class="restart_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'restart') }}> <i class="fas fa-sync"></i></a> &nbsp;
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title={{ translate('dashboard', 'kill') }}> <i class="fas fa-skull"></i></a> &nbsp;
{% elif server['stats']['updating']%}
<a data-id="{{server['server_data']['server_id']}}" class=""> UPDATING...</i></a>
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating') }}</i></a>
{% elif server['stats']['waiting_start']%}
<a data-id="{{server['server_data']['server_id']}}" class="" title={{ translate('dashboard', 'delay-explained')}}>{{ translate('dashboard', 'starting') }}</i></a>
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a>&nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play" data-toggle="tooltip" title={{ translate('dashboard', 'start') }}></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone" data-toggle="tooltip" title={{ translate('dashboard', 'clone') }}></i></a>&nbsp;
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title={{ translate('dashboard', 'kill') }}> <i class="fas fa-skull"></i></a> &nbsp;
{% end %}
{% end %}
</td>
@ -240,6 +244,25 @@ function send_command (server_id, command){
});
}
function send_kill (server_id){
/* this getCookie function is in base.html */
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/kill?id=' + server_id,
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){
location.reload();
}, 10000);
}
});
}
$( document ).ready(function() {
console.log('ready for JS!')
@ -272,6 +295,37 @@ $( document ).ready(function() {
title: '{% raw translate("dashboard", "sendingCommand") %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientRestart") %} </div>'
});
});
$( ".kill_button" ).click(function() {
server_id = $(this).attr("data-id");
bootbox.confirm({
message: "This will kill the server process and all it's subprocesses. Killing a process can potentially corrupt files. Only do this in extreme circumstances. Are you sure you would like to continue?",
buttons: {
confirm: {
label: '{% raw translate("dashboard", "kill") %}',
className: 'btn-danger'
},
cancel: {
label: '{% raw translate("panelConfig", "cancel") %}',
className: 'btn-secondary'
}
},
callback: function (result) {
if(result){
send_kill(server_id);
var dialog = bootbox.dialog({
title: '{% raw translate("dashboard", "killing") %}',
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
});
dialog.init(function(){
setTimeout(function(){
location.reload();
}, 15000);
});
}
}
});
});
if (webSocket) {
cpu_data = document.getElementById('cpu_data');

View File

@ -54,6 +54,12 @@
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart') %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop') }}</button>
</div>
{% elif data['waiting_start'] %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" title="{{ translate('serverTerm', 'delay-explained')}}">{{ translate('serverTerm', 'starting') }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart') %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop') }}</button>
</div>
{% else %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="send_command(server_id, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start') }}</button>

View File

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

View File

@ -10,7 +10,10 @@
"contact": "Contact Crafty Control Support via Discord",
"terribleFailure": "What a Terrible Failure!",
"embarassing": "Oh my, well, this is embarrassing.",
"error": "Error!"
"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."
},
"404": {
"contact": "Contact Crafty Control Support via Discord",
@ -89,7 +92,15 @@
"sendingCommand": "Sending your command",
"cpuCurFreq": "CPU Current Clock",
"cpuMaxFreq": "CPU Maximum Clock",
"cpuCores": "CPU Cores"
"cpuCores": "CPU Cores",
"start": "Start",
"stop": "Stop",
"clone": "Clone",
"kill": "Kill Process",
"restart": "Restart",
"killing": "Killing process...",
"starting": "Delayed-Start",
"delay-explained": "The service/agent has recently started and is delaying the start of the minecraft server instance"
},
"accessDenied": {
"accessDenied": "Access Denied",
@ -129,7 +140,9 @@
"start": "Start",
"restart": "Restart",
"stop": "Stop",
"updating": "Updating..."
"updating": "Updating...",
"starting": "Delayed-Start",
"delay-explained": "The service/agent has recently started and is delaying the start of the minecraft server instance"
},
"serverPlayerManagement": {
"players": "Players",

View File

@ -10,7 +10,10 @@
"contact": "Ota yhteyttä Crafty Control -tukeen Discordin kautta",
"terribleFailure": "Mikä kauhea epäonnistuminen!",
"embarassing": "No, tämähän on noloa.",
"error": "Virhe!"
"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."
},
"404": {
"contact": "Ota yhteyttä Crafty Control -tukeen Discordin kautta",
@ -89,7 +92,15 @@
"sendingCommand": "Lähetämme komentoasi",
"cpuCurFreq": "Nykyinen kellotaajuus",
"cpuMaxFreq": "Maksimi kellotaajuus",
"cpuCores": "Suorittimen ytimet"
"cpuCores": "Suorittimen ytimet",
"start": "Alkaa",
"stop": "Lopettaa",
"clone": "Klooni",
"kill": "Tapa prosessi",
"restart": "Uudelleenkäynnistää",
"killing": "Tappamisprosessi ...",
"starting": "Myöhästynyt lähtö",
"delay-explained": "Palvelu/agentti on äskettäin aloittanut ja viivästyttää minecraft -palvelimen ilmentymän alkua"
},
"accessDenied": {
"accessDenied": "Käyttö estetty",
@ -128,7 +139,10 @@
"sendCommand": "Lähetä komento",
"start": "Käynnistä",
"restart": "Uudelleen&shy;käynnistä",
"stop": "Sammuta"
"stop": "Sammuta",
"updating": "Updating",
"starting": "Myöhästynyt lähtö",
"delay-explained": "Palvelu/agentti on äskettäin aloittanut ja viivästyttää minecraft -palvelimen ilmentymän alkua"
},
"serverPlayerManagement": {
"players": "Pelaajat",

View File

@ -10,7 +10,10 @@
"contact": "Contacter le Support de Crafty Control via Discord",
"terribleFailure": "C'est une Terrible Erreur !",
"embarassing": "Oulà, c'est embarrassant.",
"error": "Erreur !"
"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."
},
"404": {
"contact": "Contacter le Support de Crafty Control via Discord",
@ -89,7 +92,15 @@
"sendingCommand": "Envoi de votre commande",
"cpuCurFreq": "Fréquence CPU actuelle",
"cpuMaxFreq": "Fréquence CPU Maximum",
"cpuCores": "Coeurs CPU"
"cpuCores": "Coeurs CPU",
"start": "Début",
"stop": "Arrêter",
"clone": "Cloner",
"kill": "Tuer le processus",
"restart": "Redémarrage",
"killing": "Processus de mise à mort...",
"starting": "Démarrage retardé",
"delay-explained": "Le service/agent a récemment démarré et retarde le démarrage de l'instance du serveur minecraft"
},
"accessDenied": {
"accessDenied": "Accès Interdit",
@ -129,7 +140,9 @@
"start": "Démarrer",
"restart": "Redémarrer",
"stop": "Arrêter",
"updating": "Mise à Jour ..."
"updating": "Mise à Jour ...",
"starting": "Démarrage retardé",
"delay-explained": "Le service/agent a récemment démarré et retarde le démarrage de l'instance du serveur minecraft"
},
"serverPlayerManagement": {
"players": "Joueurs",

View File

@ -0,0 +1,17 @@
version: '3'
services:
crafty:
container_name: crafty_commander
image: registry.gitlab.com/crafty-controller/crafty-commander:latest
ports:
- "8000:8000" # HTTP
- "8443:8443" # HTTPS
- "8123:8123" # DYNMAP
- "19132:19132/udp" # BEDROCK
- "24000-25600:24000-25600" # MC SERV PORT RANGE
volumes:
- ./docker/backups:/commander/backups
- ./docker/logs:/commander/logs
- ./docker/servers:/commander/servers
- ./docker/config:/commander/app/config

View File

@ -1,7 +1,7 @@
version: '3'
services:
cc-dashboard:
crafty:
container_name: crafty_commander
build: ..
ports:

View File

@ -131,6 +131,14 @@ if __name__ == '__main__':
# 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.")
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')))
Crafty = MainPrompt(tasks_manager, migration_manager)
def sigterm_handler(signum, current_stack_frame):