part 1 of the server builder complete
210
app/classes/minecraft/serverjars.py
Normal file
@ -0,0 +1,210 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Servers
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import requests
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class ServerJars:
|
||||
|
||||
def _get_api_result(self, call_url: str):
|
||||
base_url = "https://serverjars.com"
|
||||
full_url = "{base}{call_url}".format(base=base_url, call_url=call_url)
|
||||
|
||||
r = requests.get(full_url, timeout=2)
|
||||
|
||||
if r.status_code not in [200, 201]:
|
||||
return {}
|
||||
|
||||
try:
|
||||
api_data = json.loads(r.content)
|
||||
except Exception as e:
|
||||
logger.error("Unable to parse serverjar.com api result due to error: {}".format(e))
|
||||
return {}
|
||||
|
||||
api_result = api_data.get('status')
|
||||
api_response = api_data.get('response', {})
|
||||
|
||||
if api_result != "success":
|
||||
logger.error("Api returned a failed status: {}".format(api_result))
|
||||
return {}
|
||||
|
||||
return api_response
|
||||
|
||||
@staticmethod
|
||||
def _read_cache():
|
||||
cache_file = helper.serverjar_cache
|
||||
cache = {}
|
||||
try:
|
||||
with open(cache_file, "r") as f:
|
||||
cache = json.load(f)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to read serverjars.com cache file: {}".format(e))
|
||||
|
||||
return cache
|
||||
|
||||
def get_serverjar_data(self):
|
||||
data = self._read_cache()
|
||||
return data.get('servers')
|
||||
|
||||
@staticmethod
|
||||
def _check_api_alive():
|
||||
logger.info("Checking serverjars.com API status")
|
||||
|
||||
check_url = "https://serverjars.com/api/fetchTypes"
|
||||
r = requests.get(check_url, timeout=2)
|
||||
|
||||
if r.status_code in [200, 201]:
|
||||
logger.info("Serverjars.com API is alive")
|
||||
return True
|
||||
|
||||
logger.error("unable to contact Serverjars.com api")
|
||||
return False
|
||||
|
||||
def refresh_cache(self):
|
||||
|
||||
cache_file = helper.serverjar_cache
|
||||
cache_old = helper.is_file_older_than_x_days(cache_file)
|
||||
|
||||
# debug override
|
||||
# cache_old = True
|
||||
|
||||
# if the API is down... we bomb out
|
||||
if not self._check_api_alive():
|
||||
return False
|
||||
|
||||
logger.info("Checking Cache file age")
|
||||
# if file is older than 1 day
|
||||
|
||||
if cache_old:
|
||||
logger.info("Cache file is over 1 day old, refreshing")
|
||||
now = datetime.now()
|
||||
data = {
|
||||
'last_refreshed': now.strftime("%m/%d/%Y, %H:%M:%S"),
|
||||
'servers': {}
|
||||
}
|
||||
|
||||
jar_types = self._get_server_type_list()
|
||||
|
||||
# for each jar type
|
||||
for j in jar_types:
|
||||
|
||||
# for each server
|
||||
for s in jar_types.get(j):
|
||||
# jar versions for this server
|
||||
versions = self._get_jar_details(s)
|
||||
|
||||
# add these versions (a list) to the dict with a key of the server type
|
||||
data['servers'].update({
|
||||
s: versions
|
||||
})
|
||||
|
||||
# save our cache
|
||||
try:
|
||||
with open(cache_file, "w") as f:
|
||||
f.write(json.dumps(data, indent=4))
|
||||
logger.info("Cache file refreshed")
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to update serverjars.com cache file: {}".format(e))
|
||||
|
||||
def _get_jar_details(self, jar_type='servers'):
|
||||
url = '/api/fetchAll/{type}'.format(type=jar_type)
|
||||
response = self._get_api_result(url)
|
||||
temp = []
|
||||
for v in response:
|
||||
temp.append(v.get('version'))
|
||||
time.sleep(.5)
|
||||
return temp
|
||||
|
||||
def _get_server_type_list(self):
|
||||
url = '/api/fetchTypes/'
|
||||
response = self._get_api_result(url)
|
||||
return response
|
||||
|
||||
def download_jar(self, server, version, path):
|
||||
base_url = "https://serverjars.com/api/fetchJar/{server}/{version}".format(server=server, version=version)
|
||||
r = requests.get(base_url, timeout=2)
|
||||
if r.status_code in [200, 201]:
|
||||
|
||||
try:
|
||||
with open(path, 'bw') as output:
|
||||
output.write(r.content)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Unable to save jar to {path} due to error:{error}".format(path=path, error=e))
|
||||
pass
|
||||
|
||||
logger.error("Got {} code from download, escaping".format(r.status_code))
|
||||
return False
|
||||
|
||||
# todo: build server
|
||||
def build_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)
|
||||
jar_file = "{server}-{version}.jar".format(server=server, version=version)
|
||||
full_jar_path = os.path.join(server_dir, jar_file)
|
||||
|
||||
# make the dir - perhaps a UUID?
|
||||
helper.ensure_dir_exists(server_dir)
|
||||
|
||||
# download the jar
|
||||
self.download_jar(server, version, full_jar_path)
|
||||
|
||||
# todo: verify the MD5
|
||||
|
||||
# put data in the db
|
||||
Servers.insert({
|
||||
Servers.server_name: name,
|
||||
Servers.server_uuid: server_id,
|
||||
Servers.path: server_dir,
|
||||
Servers.executable: jar_file,
|
||||
Servers.execution_command: 'java -Xms{}G -Xmx{}G -jar /var/opt/minecraft/server/paperclip.jar nogui'.format(min_mem, max_mem),
|
||||
Servers.auto_start: False,
|
||||
Servers.auto_start_delay: 10,
|
||||
Servers.crash_detection: False,
|
||||
Servers.log_path:"{}/logs/latest.log".format(server_dir),
|
||||
Servers.stop_command:'stop'
|
||||
}).execute()
|
||||
|
||||
|
||||
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 in this directory is managed by Crafty Controller.\n Leave this file alone please")
|
||||
f.close()
|
||||
|
||||
# do a eula.txt
|
||||
with open(os.path.join(server_dir, "eula.txt"), 'w') as f:
|
||||
f.write("eula=true")
|
||||
f.close()
|
||||
|
||||
# setup server.properties with the port
|
||||
with open(os.path.join(server_dir, "server.properties"), "w") as f:
|
||||
f.write("server_port={}".format(port))
|
||||
f.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to create required server files due to :{}".format(e))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
server_jar_obj = ServerJars()
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
import string
|
||||
import base64
|
||||
@ -29,13 +30,28 @@ class Helpers:
|
||||
def __init__(self):
|
||||
self.root_dir = os.path.abspath(os.path.curdir)
|
||||
self.config_dir = os.path.join(self.root_dir, 'app', 'config')
|
||||
self.webroot = os.path.join(self.root_dir, 'app', 'frontend')
|
||||
self.servers_dir = os.path.join(self.root_dir, 'servers')
|
||||
|
||||
self.session_file = os.path.join(self.root_dir, 'session.lock')
|
||||
self.settings_file = os.path.join(self.root_dir, 'config.ini')
|
||||
self.webroot = os.path.join(self.root_dir, 'app', 'frontend')
|
||||
|
||||
self.db_path = os.path.join(self.root_dir, 'crafty.sqlite')
|
||||
self.serverjar_cache = os.path.join(self.config_dir, 'serverjars.json')
|
||||
self.passhasher = PasswordHasher()
|
||||
self.exiting = False
|
||||
|
||||
def is_file_older_than_x_days(self, file, days=1):
|
||||
if self.check_file_exists(file):
|
||||
file_time = os.path.getmtime(file)
|
||||
# Check against 24 hours
|
||||
if (time.time() - file_time) / 3600 > 24 * days:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
logger.error("{} does not exits".format(file))
|
||||
return False
|
||||
|
||||
def get_setting(self, section, key):
|
||||
|
||||
try:
|
||||
@ -254,8 +270,7 @@ class Helpers:
|
||||
return b64_bytes.decode("utf-8")
|
||||
|
||||
def create_uuid(self):
|
||||
id = str(uuid.uuid4())
|
||||
return self.base64_encode_string(id).replace("\n", '')
|
||||
return str(uuid.uuid4())
|
||||
|
||||
def ensure_dir_exists(self, path):
|
||||
"""
|
||||
|
@ -63,6 +63,7 @@ class Host_Stats(BaseModel):
|
||||
class Servers(BaseModel):
|
||||
server_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_uuid = CharField(default="")
|
||||
server_name = CharField(default="Server")
|
||||
path = CharField(default="")
|
||||
executable = CharField(default="")
|
||||
@ -139,9 +140,14 @@ class db_shortcuts:
|
||||
def return_rows(self, query):
|
||||
rows = []
|
||||
|
||||
if query:
|
||||
try:
|
||||
if query.count() > 0:
|
||||
for s in query:
|
||||
rows.append(model_to_dict(s))
|
||||
except Exception as e:
|
||||
logger.warning("Database Error: {}".format(e))
|
||||
pass
|
||||
|
||||
return rows
|
||||
|
||||
def get_all_defined_servers(self):
|
||||
|
@ -11,6 +11,7 @@ from app.classes.web.tornado import webserver
|
||||
from app.classes.minecraft import server_props
|
||||
from app.classes.minecraft.stats import stats
|
||||
from app.classes.minecraft.controller import controller
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -90,5 +91,13 @@ class TasksManager:
|
||||
console.info("Stats collection frequency set to {stats} seconds".format(stats=stats_update_frequency))
|
||||
schedule.every(stats_update_frequency).seconds.do(stats.record_stats)
|
||||
|
||||
@staticmethod
|
||||
def serverjar_cache_refresher():
|
||||
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)
|
||||
|
||||
|
||||
tasks_manager = TasksManager()
|
||||
|
@ -8,7 +8,11 @@ logger = logging.getLogger(__name__)
|
||||
class DefaultHandler(BaseHandler):
|
||||
|
||||
# Override prepare() instead of get() to cover all possible HTTP methods.
|
||||
def prepare(self):
|
||||
def prepare(self, page=None):
|
||||
print(page)
|
||||
if page is not None:
|
||||
self.set_status(404)
|
||||
self.render("public/404.html")
|
||||
else:
|
||||
self.redirect("/public/login")
|
||||
|
||||
|
@ -40,27 +40,28 @@ class PublicHandler(BaseHandler):
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
|
||||
error = bleach.clean(self.get_argument('error', "Invalid Login!"))
|
||||
|
||||
page_data = {
|
||||
'version': helper.get_version_string()
|
||||
'version': helper.get_version_string(),
|
||||
'error': error
|
||||
}
|
||||
|
||||
error = bleach.clean(self.get_argument('error', ""))
|
||||
|
||||
if error:
|
||||
error_msg = "Invalid Login!"
|
||||
else:
|
||||
error_msg = ""
|
||||
|
||||
# sensible defaults
|
||||
template = "public/404.html"
|
||||
|
||||
if page == "login":
|
||||
template = "public/login.html"
|
||||
page_data['error'] = error_msg
|
||||
|
||||
elif page == 404:
|
||||
template = "public/404.html"
|
||||
|
||||
elif page == "error":
|
||||
template = "public/error.html"
|
||||
|
||||
# if we have no page, let's go to login
|
||||
else:
|
||||
template = "public/404.html"
|
||||
self.redirect('/public/login')
|
||||
|
||||
self.render(template, data=page_data)
|
||||
|
||||
|
@ -1,18 +1,26 @@
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import bleach
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Users, installer
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.minecraft.controller import controller
|
||||
from app.classes.shared.models import db_helper
|
||||
from app.classes.shared.models import db_helper, Servers
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class ServerHandler(BaseHandler):
|
||||
|
||||
@ -35,10 +43,44 @@ class ServerHandler(BaseHandler):
|
||||
'hosts_data': db_helper.get_latest_hosts_stats()
|
||||
|
||||
}
|
||||
# print(page_data['hosts_data'])
|
||||
|
||||
if page == "step1":
|
||||
|
||||
page_data['server_types'] = server_jar_obj.get_serverjar_data()
|
||||
template = "server/wizard.html"
|
||||
|
||||
self.render(
|
||||
template,
|
||||
data=page_data
|
||||
)
|
||||
|
||||
@tornado.web.authenticated
|
||||
def post(self, page):
|
||||
|
||||
user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
|
||||
template = "public/404.html"
|
||||
page_data = {
|
||||
'version_data': "version_data_here",
|
||||
'user_data': user_data,
|
||||
}
|
||||
|
||||
print(page)
|
||||
|
||||
if page == "step1":
|
||||
|
||||
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', ''))
|
||||
max_mem = bleach.clean(self.get_argument('max_memory', ''))
|
||||
port = bleach.clean(self.get_argument('port', ''))
|
||||
|
||||
server_parts = server.split("|")
|
||||
|
||||
success = server_jar_obj.build_server(server_parts[0], server_parts[1],server_name,min_mem, max_mem, port)
|
||||
if success:
|
||||
self.redirect("/panel/dashboard")
|
||||
|
||||
|
||||
self.render(
|
||||
template,
|
||||
|
@ -3,7 +3,10 @@
|
||||
"disable_existing_loggers": false,
|
||||
"formatters": {
|
||||
"commander": {
|
||||
"format": "%(asctime)s - [Commander] - %(levelname)-8s - %(name)s - %(message)s"
|
||||
"format": "%(asctime)s - [Crafty] - %(levelname)-8s - %(name)s - %(message)s"
|
||||
},
|
||||
"tornado_access": {
|
||||
"format": "%(asctime)s - [Tornado] - [Access] - %(levelname)s - %(message)s"
|
||||
}
|
||||
},
|
||||
|
||||
@ -28,6 +31,14 @@
|
||||
"filename": "logs/session.log",
|
||||
"backupCount": 0,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"tornado_access_file_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "tornado_access",
|
||||
"filename": "logs/tornado-access.log",
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 20,
|
||||
"encoding": "utf8"
|
||||
}
|
||||
},
|
||||
|
||||
@ -36,6 +47,11 @@
|
||||
"level": "INFO",
|
||||
"handlers": ["main_file_handler", "session_file_handler"],
|
||||
"propagate": false
|
||||
},
|
||||
"tornado.access": {
|
||||
"level": "INFO",
|
||||
"handlers": ["tornado_access_file_handler"],
|
||||
"propagate": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
344
app/config/serverjars.json
Normal file
@ -0,0 +1,344 @@
|
||||
{
|
||||
"last_refreshed": "08/23/2020, 17:45:25",
|
||||
"servers": {
|
||||
"nukkitx": [
|
||||
"1.14"
|
||||
],
|
||||
"pocketmine": [
|
||||
"1.14",
|
||||
"1.13",
|
||||
"1.12",
|
||||
"1.11",
|
||||
"1.10",
|
||||
"1.9",
|
||||
"1.8",
|
||||
"1.7",
|
||||
"1.6",
|
||||
"1.5",
|
||||
"1.4"
|
||||
],
|
||||
"magma": [
|
||||
"1.12.2"
|
||||
],
|
||||
"mohist": [
|
||||
"1.12.2"
|
||||
],
|
||||
"travertine": [
|
||||
"1.16",
|
||||
"1.15",
|
||||
"1.14",
|
||||
"1.13",
|
||||
"1.12",
|
||||
"1.11",
|
||||
"1.10",
|
||||
"1.9",
|
||||
"1.8",
|
||||
"1.7"
|
||||
],
|
||||
"bungeecord": [
|
||||
"1.16",
|
||||
"1.15",
|
||||
"1.14",
|
||||
"1.13",
|
||||
"1.12",
|
||||
"1.11",
|
||||
"1.10",
|
||||
"1.9",
|
||||
"1.8"
|
||||
],
|
||||
"velocity": [
|
||||
"1.15",
|
||||
"1.14",
|
||||
"1.13",
|
||||
"1.12",
|
||||
"1.11",
|
||||
"1.10",
|
||||
"1.9",
|
||||
"1.8"
|
||||
],
|
||||
"waterfall": [
|
||||
"1.16",
|
||||
"1.15",
|
||||
"1.14",
|
||||
"1.13",
|
||||
"1.12",
|
||||
"1.11",
|
||||
"1.10",
|
||||
"1.9",
|
||||
"1.8"
|
||||
],
|
||||
"bukkit": [
|
||||
"1.16.2",
|
||||
"1.16.1",
|
||||
"1.15.2",
|
||||
"1.15.1",
|
||||
"1.15",
|
||||
"1.14.4",
|
||||
"1.14.3",
|
||||
"1.14.2",
|
||||
"1.14.1",
|
||||
"1.14",
|
||||
"1.13.2",
|
||||
"1.13.1",
|
||||
"1.13",
|
||||
"1.12.2",
|
||||
"1.12.1",
|
||||
"1.12",
|
||||
"1.11.2",
|
||||
"1.11.1",
|
||||
"1.11",
|
||||
"1.10.2",
|
||||
"1.10",
|
||||
"1.9.4",
|
||||
"1.9.2",
|
||||
"1.9",
|
||||
"1.8.8",
|
||||
"1.8"
|
||||
],
|
||||
"paper": [
|
||||
"1.16.1",
|
||||
"1.15.2",
|
||||
"1.15.1",
|
||||
"1.15",
|
||||
"1.14.4",
|
||||
"1.14.3",
|
||||
"1.14.2",
|
||||
"1.14.1",
|
||||
"1.14",
|
||||
"1.13.2",
|
||||
"1.13.1",
|
||||
"1.13",
|
||||
"1.12.2",
|
||||
"1.12.1",
|
||||
"1.12",
|
||||
"1.11.2",
|
||||
"1.10.2",
|
||||
"1.9.4",
|
||||
"1.8.8"
|
||||
],
|
||||
"spigot": [
|
||||
"1.16.2",
|
||||
"1.16.1",
|
||||
"1.15.2",
|
||||
"1.15.1",
|
||||
"1.15",
|
||||
"1.14.4",
|
||||
"1.14.3",
|
||||
"1.14.2",
|
||||
"1.14.1",
|
||||
"1.14",
|
||||
"1.13.2",
|
||||
"1.13.1",
|
||||
"1.13",
|
||||
"1.12.2",
|
||||
"1.12.1",
|
||||
"1.12",
|
||||
"1.11.2",
|
||||
"1.11.1",
|
||||
"1.11",
|
||||
"1.10.2",
|
||||
"1.10",
|
||||
"1.9.4",
|
||||
"1.9.2",
|
||||
"1.9",
|
||||
"1.8.8"
|
||||
],
|
||||
"snapshot": [
|
||||
"1.16pre8",
|
||||
"1.16pre7",
|
||||
"1.16pre6",
|
||||
"1.16pre5",
|
||||
"1.16pre4",
|
||||
"1.16pre3",
|
||||
"1.16pre2",
|
||||
"1.16pre1",
|
||||
"1.1620w30a",
|
||||
"1.1620w29a",
|
||||
"1.1620w28a",
|
||||
"1.1620w27a",
|
||||
"1.1620w22a",
|
||||
"1.1620w21a",
|
||||
"1.1620w20b",
|
||||
"1.1620w20a",
|
||||
"1.1620w19a",
|
||||
"1.1620w18a",
|
||||
"1.1620w17a",
|
||||
"1.1620w16a",
|
||||
"1.1620w15a",
|
||||
"1.1620w14a",
|
||||
"1.1620w14infinite",
|
||||
"1.1620w13b",
|
||||
"1.1620w13a",
|
||||
"1.1620w12a",
|
||||
"1.16rc1",
|
||||
"1.1620w11a",
|
||||
"1.1620w10a",
|
||||
"1.1620w09a",
|
||||
"1.1620w08a",
|
||||
"1.1620w07a",
|
||||
"1.16.2rc2",
|
||||
"1.16.2rc1",
|
||||
"1.1620w06a",
|
||||
"1.16.2pre3",
|
||||
"1.16.2pre2",
|
||||
"1.16.2pre1",
|
||||
"1.16.2",
|
||||
"1.161.16.1",
|
||||
"1.16.1",
|
||||
"1.16",
|
||||
"1.15pre7",
|
||||
"1.15pre6",
|
||||
"1.15pre5",
|
||||
"1.15pre4",
|
||||
"1.15pre3",
|
||||
"1.15pre2",
|
||||
"1.15pre1",
|
||||
"1.1519w46b",
|
||||
"1.1519w46a",
|
||||
"1.1519w45b",
|
||||
"1.1519w45a",
|
||||
"1.1519w44a",
|
||||
"1.1519w42a",
|
||||
"1.1519w41a",
|
||||
"1.1519w40a",
|
||||
"1.1519w39a",
|
||||
"1.1519w38b",
|
||||
"1.1519w38a",
|
||||
"1.1519w37a",
|
||||
"1.1519w36a",
|
||||
"1.1519w35a",
|
||||
"1.1519w34a",
|
||||
"1.15.2pre2",
|
||||
"1.15.2pre1",
|
||||
"1.15.1pre1",
|
||||
"1.14pre5",
|
||||
"1.14pre4",
|
||||
"1.14pre3",
|
||||
"1.14pre2",
|
||||
"1.14pre1",
|
||||
"1.1419w14b",
|
||||
"1.1419w14a",
|
||||
"1.1419w13b",
|
||||
"1.1419w13a",
|
||||
"1.1419w12b",
|
||||
"1.1419w12a",
|
||||
"1.1419w11b",
|
||||
"1.1419w11a",
|
||||
"1.1419w09a",
|
||||
"1.1419w08b",
|
||||
"1.1419w08a",
|
||||
"1.1419w07a",
|
||||
"1.1419w06a",
|
||||
"1.1419w05a",
|
||||
"1.1419w04b",
|
||||
"1.1419w04a",
|
||||
"1.1419w03b",
|
||||
"1.1419w03a",
|
||||
"1.1419w03c",
|
||||
"1.1419w02a",
|
||||
"1.1418w50a",
|
||||
"1.1418w49a",
|
||||
"1.1418w48b",
|
||||
"1.1418w48a",
|
||||
"1.1418w47b",
|
||||
"1.1418w47a",
|
||||
"1.1418w46a",
|
||||
"1.1418w45a",
|
||||
"1.1418w44a",
|
||||
"1.1418w43b",
|
||||
"1.1418w43a",
|
||||
"1.1418w43c",
|
||||
"1.14.4pre7",
|
||||
"1.14.4pre6",
|
||||
"1.14.4pre5",
|
||||
"1.14.4pre4",
|
||||
"1.14.4pre3",
|
||||
"1.14.4pre2",
|
||||
"1.14.4pre1",
|
||||
"1.14.3pre4",
|
||||
"1.14.3pre3",
|
||||
"1.14.3pre2",
|
||||
"1.14.3pre1",
|
||||
"1.14.2pre4",
|
||||
"1.14.2pre3",
|
||||
"1.14.2pre2",
|
||||
"1.14.2pre1",
|
||||
"1.14.1pre2",
|
||||
"1.14.1pre1"
|
||||
],
|
||||
"vanilla": [
|
||||
"1.16.2",
|
||||
"1.16.1",
|
||||
"1.16",
|
||||
"1.15.2",
|
||||
"1.15.1",
|
||||
"1.15",
|
||||
"1.14.4",
|
||||
"1.14.3",
|
||||
"1.14.2",
|
||||
"1.14.1",
|
||||
"1.14",
|
||||
"1.13.2",
|
||||
"1.13.1",
|
||||
"1.13",
|
||||
"1.12.2",
|
||||
"1.12.1",
|
||||
"1.12",
|
||||
"1.11.2",
|
||||
"1.11.1",
|
||||
"1.11",
|
||||
"1.10.2",
|
||||
"1.10.1",
|
||||
"1.10",
|
||||
"1.9.4",
|
||||
"1.9.3",
|
||||
"1.9.2",
|
||||
"1.9.1",
|
||||
"1.9",
|
||||
"1.8.8",
|
||||
"1.8.7",
|
||||
"1.8.6",
|
||||
"1.8.5",
|
||||
"1.8.4",
|
||||
"1.8.3",
|
||||
"1.8.2",
|
||||
"1.8.1",
|
||||
"1.8",
|
||||
"1.7.10",
|
||||
"1.7.9",
|
||||
"1.7.8",
|
||||
"1.7.7",
|
||||
"1.7.6",
|
||||
"1.7.5",
|
||||
"1.7.4",
|
||||
"1.7.3",
|
||||
"1.7.2",
|
||||
"1.7.1",
|
||||
"1.7",
|
||||
"1.6.4",
|
||||
"1.6.3",
|
||||
"1.6.2",
|
||||
"1.6.1",
|
||||
"1.6",
|
||||
"1.5.2",
|
||||
"1.5.1",
|
||||
"1.5",
|
||||
"1.4.7",
|
||||
"1.4.6",
|
||||
"1.4.5",
|
||||
"1.4.4",
|
||||
"1.4.3",
|
||||
"1.4.2",
|
||||
"1.4.1",
|
||||
"1.4",
|
||||
"1.3.2",
|
||||
"1.3.1",
|
||||
"1.3",
|
||||
"1.2.5",
|
||||
"1.2.4",
|
||||
"1.2.3",
|
||||
"1.2.2",
|
||||
"1.2.1"
|
||||
]
|
||||
}
|
||||
}
|
9
app/frontend/static/assets/css/crafty.css
Normal file
@ -0,0 +1,9 @@
|
||||
.select-css option {
|
||||
background-color: #1C1E2F;
|
||||
color: white
|
||||
}
|
||||
|
||||
.select-css option:checked {
|
||||
background-color: #1C1E2F;
|
||||
color: white
|
||||
}
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 231 KiB |
Before Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 933 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 966 B |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 581 B |
Before Width: | Height: | Size: 862 B |
Before Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 659 B |
Before Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 477 B |
Before Width: | Height: | Size: 515 B |
Before Width: | Height: | Size: 744 B |
Before Width: | Height: | Size: 385 B |
Before Width: | Height: | Size: 473 B |
Before Width: | Height: | Size: 414 B |
Before Width: | Height: | Size: 398 B |
Before Width: | Height: | Size: 271 B |
Before Width: | Height: | Size: 369 B |
Before Width: | Height: | Size: 516 B |
Before Width: | Height: | Size: 549 B |
Before Width: | Height: | Size: 374 B |
Before Width: | Height: | Size: 596 B |
Before Width: | Height: | Size: 708 B |
Before Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 81 KiB |
@ -1,13 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="35" height="35" viewBox="0 0 35 35">
|
||||
<defs>
|
||||
<circle id="a" cx="17.5" cy="17.5" r="17.5"/>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<mask id="b" fill="#fff">
|
||||
<use xlink:href="#a"/>
|
||||
</mask>
|
||||
<use fill="#0DC26D" xlink:href="#a"/>
|
||||
<path fill="#FFF" d="M17.5 0H35v17.5H17.5z" mask="url(#b)"/>
|
||||
<path fill="#EFF3F6" d="M17.5 17.5H35V35H17.5z" mask="url(#b)" opacity=".5"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 546 B |
@ -1 +0,0 @@
|
||||
<svg id="SvgjsSvg1011" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="18" height="19" viewBox="0 0 18 19"><title>Shape</title><desc>Created with Avocode.</desc><defs id="SvgjsDefs1012"></defs><path id="SvgjsPath1013" d="M603.64 503.84C603.64 503.2 603.58 502.59 603.48 502L595 502L595 505.48L599.84 505.48C599.64 506.61 599 507.56 598.0500000000001 508.20000000000005L598.0500000000001 510.46000000000004L600.96 510.46000000000004C602.6600000000001 508.89000000000004 603.64 506.58000000000004 603.64 503.84000000000003Z " fill="#4285f4" fill-opacity="1" transform="matrix(1,0,0,1,-586,-495)"></path><path id="SvgjsPath1014" d="M595.04 513.29C597.4699999999999 513.29 599.51 512.48 601 511.10999999999996L598.09 508.84999999999997C597.2800000000001 509.39 596.25 509.71 595.0400000000001 509.71C592.7 509.71 590.71 508.13 590.0100000000001 506L587.0000000000001 506L587.0000000000001 508.33C588.4800000000001 511.27 591.5200000000001 513.29 595.0400000000001 513.29Z " fill="#34a853" fill-opacity="1" transform="matrix(1,0,0,1,-586,-495)"></path><path id="SvgjsPath1015" d="M589.96 505.75C589.7800000000001 505.21 589.6800000000001 504.64 589.6800000000001 504.04C589.6800000000001 503.45000000000005 589.7800000000001 502.87 589.96 502.33000000000004L589.96 500.00000000000006L586.96 500.00000000000006C586.35 501.2200000000001 586 502.59000000000003 586 504.0400000000001C586 505.49000000000007 586.35 506.87000000000006 586.96 508.0800000000001Z " fill="#fbbc05" fill-opacity="1" transform="matrix(1,0,0,1,-586,-495)"></path><path id="SvgjsPath1016" d="M595.04 498.58C596.36 498.58 597.55 499.03 598.48 499.93L601.0600000000001 497.34000000000003C599.5100000000001 495.89000000000004 597.47 495.00000000000006 595.0400000000001 495.00000000000006C591.5200000000001 495.00000000000006 588.4800000000001 497.02000000000004 587.0000000000001 499.96000000000004L590.0100000000001 502.29C590.7100000000002 500.16 592.7000000000002 498.58000000000004 595.0400000000001 498.58000000000004Z " fill="#ea4335" fill-opacity="1" transform="matrix(1,0,0,1,-586,-495)"></path></svg>
|
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 829 KiB |
Before Width: | Height: | Size: 292 KiB |
Before Width: | Height: | Size: 758 KiB |
Before Width: | Height: | Size: 674 KiB |
Before Width: | Height: | Size: 683 KiB |
Before Width: | Height: | Size: 744 KiB |
Before Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 872 KiB |
Before Width: | Height: | Size: 348 KiB |
Before Width: | Height: | Size: 923 KiB |
Before Width: | Height: | Size: 443 KiB |
Before Width: | Height: | Size: 476 KiB |
Before Width: | Height: | Size: 399 KiB |
Before Width: | Height: | Size: 708 KiB |
Before Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 59 KiB |