mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'enhancement/add-storage-to-dash' into 'dev'
Add host storage display to the dashboard See merge request crafty-controller/crafty-4!551
This commit is contained in:
commit
206941e26e
@ -5,6 +5,7 @@
|
||||
- Add ignored exit codes for crash detection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/553))
|
||||
- Allow users to change the directory where Crafty Stores Servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/539)) <br>
|
||||
*(Only for non-docker, docker users should change host volume mount)*
|
||||
- Add host storage display option to the dashboard ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/551))
|
||||
### Bug fixes
|
||||
- Fix exception related to page data on server start ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/544))
|
||||
- Fix logical issue with uploading dynamic files ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/555))
|
||||
|
@ -417,6 +417,7 @@ class Helpers:
|
||||
"allow_nsfw_profile_pictures": False,
|
||||
"enable_user_self_delete": False,
|
||||
"reset_secrets_on_next_boot": False,
|
||||
"monitored_mounts": Helpers.get_all_mounts(),
|
||||
}
|
||||
|
||||
def get_all_settings(self):
|
||||
@ -435,6 +436,14 @@ class Helpers:
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_all_mounts():
|
||||
mounts = []
|
||||
for item in psutil.disk_partitions(all=False):
|
||||
mounts.append(item.mountpoint)
|
||||
|
||||
return mounts
|
||||
|
||||
def is_subdir(self, server_path, root_dir):
|
||||
server_path = os.path.realpath(server_path)
|
||||
root_dir = os.path.realpath(root_dir)
|
||||
@ -1040,9 +1049,9 @@ class Helpers:
|
||||
if filename not in self.ignored_names:
|
||||
output += f"""<li id="{dpath}li" class="tree-item"
|
||||
data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
data-name="{filename}" onclick="getDirView(event)">
|
||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
||||
@ -1081,9 +1090,9 @@ class Helpers:
|
||||
if filename not in self.ignored_names:
|
||||
output += f"""<li id="{dpath}li" class="tree-item"
|
||||
data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
data-name="{filename}" onclick="getDirView(event)">
|
||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
||||
|
@ -4,6 +4,7 @@ import logging
|
||||
import threading
|
||||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from tzlocal import get_localzone
|
||||
from tzlocal.utils import ZoneInfoNotFoundError
|
||||
@ -686,18 +687,37 @@ class TasksManager:
|
||||
host_stats = HelpersManagement.get_latest_hosts_stats()
|
||||
if len(self.helper.websocket_helper.clients) > 0:
|
||||
# There are clients
|
||||
self.helper.websocket_helper.broadcast_page(
|
||||
"/panel/dashboard",
|
||||
"update_host_stats",
|
||||
{
|
||||
"cpu_usage": host_stats.get("cpu_usage"),
|
||||
"cpu_cores": host_stats.get("cpu_cores"),
|
||||
"cpu_cur_freq": host_stats.get("cpu_cur_freq"),
|
||||
"cpu_max_freq": host_stats.get("cpu_max_freq"),
|
||||
"mem_percent": host_stats.get("mem_percent"),
|
||||
"mem_usage": host_stats.get("mem_usage"),
|
||||
},
|
||||
)
|
||||
try:
|
||||
self.helper.websocket_helper.broadcast_page(
|
||||
"/panel/dashboard",
|
||||
"update_host_stats",
|
||||
{
|
||||
"cpu_usage": host_stats.get("cpu_usage"),
|
||||
"cpu_cores": host_stats.get("cpu_cores"),
|
||||
"cpu_cur_freq": host_stats.get("cpu_cur_freq"),
|
||||
"cpu_max_freq": host_stats.get("cpu_max_freq"),
|
||||
"mem_percent": host_stats.get("mem_percent"),
|
||||
"mem_usage": host_stats.get("mem_usage"),
|
||||
"disk_usage": json.loads(
|
||||
host_stats.get("disk_json").replace("'", '"')
|
||||
),
|
||||
"mounts": self.helper.get_setting("monitored_mounts"),
|
||||
},
|
||||
)
|
||||
except:
|
||||
self.helper.websocket_helper.broadcast_page(
|
||||
"/panel/dashboard",
|
||||
"update_host_stats",
|
||||
{
|
||||
"cpu_usage": host_stats.get("cpu_usage"),
|
||||
"cpu_cores": host_stats.get("cpu_cores"),
|
||||
"cpu_cur_freq": host_stats.get("cpu_cur_freq"),
|
||||
"cpu_max_freq": host_stats.get("cpu_max_freq"),
|
||||
"mem_percent": host_stats.get("mem_percent"),
|
||||
"mem_usage": host_stats.get("mem_usage"),
|
||||
"disk_usage": {},
|
||||
},
|
||||
)
|
||||
time.sleep(1)
|
||||
|
||||
def check_for_updates(self):
|
||||
|
@ -294,6 +294,7 @@ class PanelHandler(BaseHandler):
|
||||
"background": self.controller.cached_login,
|
||||
"login_opacity": self.controller.management.get_login_opacity(),
|
||||
"serverTZ": tz,
|
||||
"monitored": self.helper.get_setting("monitored_mounts"),
|
||||
"version_data": self.helper.get_version_string(),
|
||||
"failed_servers": self.controller.servers.failed_servers,
|
||||
"user_data": exec_user,
|
||||
@ -333,7 +334,12 @@ class PanelHandler(BaseHandler):
|
||||
else None,
|
||||
"superuser": superuser,
|
||||
}
|
||||
|
||||
try:
|
||||
page_data["hosts_data"]["disk_json"] = json.loads(
|
||||
page_data["hosts_data"]["disk_json"].replace("'", '"')
|
||||
)
|
||||
except:
|
||||
page_data["hosts_data"]["disk_json"] = {}
|
||||
if page == "unauthorized":
|
||||
template = "panel/denied.html"
|
||||
|
||||
@ -884,6 +890,7 @@ class PanelHandler(BaseHandler):
|
||||
page_data["config-json"] = data
|
||||
page_data["availables_languages"] = []
|
||||
page_data["all_languages"] = []
|
||||
page_data["all_partitions"] = self.helper.get_all_mounts()
|
||||
|
||||
for file in sorted(
|
||||
os.listdir(
|
||||
|
@ -71,7 +71,7 @@
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) {
|
||||
$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')
|
||||
});">Enable all Languages</button>
|
||||
});">{{ translate('panelConfig', 'enableLang', data['lang']) }}</button>
|
||||
<select id="lang_select" class="form-control selectpicker show-tick" data-icon-base="fas"
|
||||
data-tick-icon="fa-check" multiple data-style="custom-picker">
|
||||
{% for lang in data['all_languages'] %}
|
||||
@ -86,6 +86,25 @@
|
||||
rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}"
|
||||
hidden>{{','.join(item[1])}}</textarea>
|
||||
</div>
|
||||
{% elif item[0] == 'monitored_mounts'%}
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#mount_select')).each(function(element) {
|
||||
$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')
|
||||
});">{{ translate('panelConfig', 'noMounts', data['lang']) }}</button>
|
||||
<select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas"
|
||||
data-tick-icon="fa-check" multiple data-style="custom-picker">
|
||||
{% for mount in data['all_partitions'] %}
|
||||
{% if mount in item[1] %}
|
||||
<option selected>{{mount}}</option>
|
||||
{% else %}
|
||||
<option>{{mount}}</option>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</select>
|
||||
<textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden"
|
||||
rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}"
|
||||
hidden>{{','.join(item[1])}}</textarea>
|
||||
</div>
|
||||
{% elif isinstance(item[1], list) %}
|
||||
<textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}"
|
||||
class="form-control list">{{','.join(item[1])}}</textarea>
|
||||
@ -160,6 +179,9 @@
|
||||
let selected_Lang = $('#lang_select').val();
|
||||
$('#disabled_lang').val(selected_Lang);
|
||||
|
||||
let mounts = $('#mount_select').val();
|
||||
$('#monitored_mounts').val(mounts);
|
||||
|
||||
let class_list = document.getElementsByClassName("list");
|
||||
let form_json = convertFormToJSON($("#config-form"));
|
||||
for (let i = 0; i < class_list.length; i++) {
|
||||
|
@ -42,7 +42,6 @@
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% end %}
|
||||
<div class="row">
|
||||
@ -50,7 +49,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="col-xl-4 col-md-5">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}
|
||||
@ -72,7 +71,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
|
||||
<div class="col-xl-4 col-md-4 mt-md-0 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}
|
||||
@ -88,7 +87,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
|
||||
<div class="col-xl-4 col-md-3 mt-md-0 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}
|
||||
@ -101,6 +100,43 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper" style="width: 100%;">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">Storage
|
||||
</h5>
|
||||
<div id="storage_data">
|
||||
<div class="row">
|
||||
{% for item in data.get('hosts_data').get('disk_json') %}
|
||||
{% if item["mount"] in data["monitored"] %}
|
||||
<div id="{{item['device']}}" class="col-xl-3 col-lg-3 col-md-4 col-12">
|
||||
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading"
|
||||
id="title_{{item['device']}}" data-toggle="tooltip" data-placement="bottom"
|
||||
title="{{item['mount']}}" style="max-width: 100%;"><i class="fas fa-hdd"></i>
|
||||
{{item["mount"]}}</h4>
|
||||
<div class="progress d-inline-block"
|
||||
style="height: 20px; width: 100%; background-color: rgb(139, 139, 139) !important;">
|
||||
<div class="progress-bar
|
||||
{% if item['percent_used'] <= 58 %}
|
||||
bg-success
|
||||
{% elif 59 <= item['percent_used'] <= 75 %}
|
||||
bg-warning
|
||||
{% else %}
|
||||
bg-danger
|
||||
{% end %}
|
||||
" role="progressbar" style="color: black; height: 100%; width: {{item['percent_used']}}%;"
|
||||
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">{{item["used"]}} /
|
||||
{{item["total"]}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -891,6 +927,32 @@
|
||||
cpu_usage.textContent = hostStats.cpu_usage;
|
||||
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
|
||||
mem_percent.textContent = hostStats.mem_percent + '%';
|
||||
|
||||
var storage_html = '<div class="row">';
|
||||
for (i = 0; i < hostStats.disk_usage.length; i++) {
|
||||
if (hostStats.mounts.includes(hostStats.disk_usage[i].mount)) {
|
||||
storage_html += `<div id="{{item['device']}}" class="col-xl-3 col-lg-3 col-md-4 col-12">
|
||||
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading" id="title_{{item['device']}}" data-toggle="tooltip" data-placement="bottom" title="${hostStats.disk_usage[i].mount}" style="max-width: 100%;"><i class="fas fa-hdd"></i> ${hostStats.disk_usage[i].mount}</h4>
|
||||
<div class="progress" style="display: inline-block; height: 20px; width: 100%; background-color: rgb(139, 139, 139) !important;">
|
||||
<div class="progress-bar`;
|
||||
if (hostStats.disk_usage[i].percent_used <= 58) {
|
||||
storage_html += ` bg-success`;
|
||||
} else if (hostStats.disk_usage[i].percent_used <= 75 && hostStats.disk_usage[i].percent_used >= 59) {
|
||||
storage_html += ` bg-warning`;
|
||||
} else {
|
||||
storage_html += ` bg-danger`;
|
||||
}
|
||||
storage_html += `" role="progressbar" style="color: black; height: 100%; width: ${hostStats.disk_usage[i].percent_used}%;"
|
||||
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">${hostStats.disk_usage[i].used} / ${hostStats.disk_usage[i].total}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
storage_html += `</div>`;
|
||||
$(".storage-heading").tooltip('hide');
|
||||
$("#storage_data").html(storage_html);
|
||||
$("#storage_data").tooltip({ selector: '.storage-heading' });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -233,6 +233,8 @@
|
||||
"user": "User",
|
||||
"users": "Users",
|
||||
"title": "Crafty Configuration",
|
||||
"enableLang": "Enable All Languages",
|
||||
"noMounts": "Show no Mounts on Dash",
|
||||
"globalServer": "Global Servers Directory",
|
||||
"globalExplain": "Where Crafty stores all your server files. (We will append the path with /servers/[uuid of server])"
|
||||
},
|
||||
@ -610,4 +612,4 @@
|
||||
"manager": "Manager",
|
||||
"selectManager": "Select Manager for User"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user