Get and set pretty hostname to use as system name in Web UI (#997)

- Display the pretty hostname as system name in header
- Move IP and hostname down to the footer
- New endpoint for setting the pretty hostname, plus form field in the Web UI
- (unrelated) Use platform.uname() instead of shell uname
- (unrelated) Better logic for fetching the Mac HD Drivers zip file in easyinstall.sh
This commit is contained in:
Daniel Markstedt 2022-11-20 10:20:32 -08:00 committed by GitHub
parent 2645656199
commit 0d95890887
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 42 deletions

View File

@ -19,7 +19,10 @@ RUN apt-get update \
RUN groupadd pi RUN groupadd pi
RUN useradd --create-home --shell /bin/bash -g pi pi RUN useradd --create-home --shell /bin/bash -g pi pi
RUN echo "pi ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers RUN echo "pi ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
RUN echo "pi:rascsi" | chpasswd RUN echo "pi:rascsi" | chpasswd
# Allows custom PATH for mock commands to work when executing with sudo
RUN sed -i 's/^Defaults\tsecure_path/#Defaults\tsecure_path./' /etc/sudoers
RUN mkdir /home/pi/shared_files RUN mkdir /home/pi/shared_files
RUN touch /etc/dhcpcd.conf RUN touch /etc/dhcpcd.conf

View File

@ -11,6 +11,9 @@ fi
# Start Nginx service # Start Nginx service
nginx nginx
# Use mock commands
export PATH="/home/pi/RASCSI/python/web/mock/bin:$PATH"
# Pass args to web UI start script # Pass args to web UI start script
if [[ $RASCSI_PASSWORD ]]; then if [[ $RASCSI_PASSWORD ]]; then
/home/pi/RASCSI/python/web/start.sh "$@" --password=$RASCSI_PASSWORD /home/pi/RASCSI/python/web/start.sh "$@" --password=$RASCSI_PASSWORD

View File

@ -626,11 +626,13 @@ function installHfdisk() {
# Fetch HFS drivers that the Web Interface uses # Fetch HFS drivers that the Web Interface uses
function fetchHardDiskDrivers() { function fetchHardDiskDrivers() {
if [ ! -d "$BASE/mac-hard-disk-drivers" ]; then DRIVER_ARCHIVE="mac-hard-disk-drivers"
if [ ! -d "$BASE/$DRIVER_ARCHIVE" ]; then
cd "$BASE" || exit 1 cd "$BASE" || exit 1
wget -r https://www.dropbox.com/s/gcs4v5pcmk7rxtb/mac-hard-disk-drivers.zip?dl=0 # -N option overwrites if downloaded file is newer than existing file
unzip -d mac-hard-disk-drivers mac-hard-disk-drivers.zip wget -N "https://www.dropbox.com/s/gcs4v5pcmk7rxtb/$DRIVER_ARCHIVE.zip?dl=1" -O "$DRIVER_ARCHIVE.zip"
rm mac-hard-disk-drivers.zip unzip -d "$DRIVER_ARCHIVE" "$DRIVER_ARCHIVE.zip"
rm "$DRIVER_ARCHIVE.zip"
fi fi
} }

View File

@ -3,11 +3,12 @@ Module with methods that interact with the Pi system
""" """
import subprocess import subprocess
import logging import logging
from subprocess import run from subprocess import run, CalledProcessError
from shutil import disk_usage from shutil import disk_usage
from re import findall, match from re import findall, match
from socket import socket, gethostname, AF_INET, SOCK_DGRAM from socket import socket, gethostname, AF_INET, SOCK_DGRAM
from pathlib import Path from pathlib import Path
from platform import uname
from rascsi.common_settings import SHELL_ERROR from rascsi.common_settings import SHELL_ERROR
@ -37,20 +38,6 @@ class SysCmds:
logging.warning(SHELL_ERROR, error.cmd, error.stderr.decode("utf-8")) logging.warning(SHELL_ERROR, error.cmd, error.stderr.decode("utf-8"))
ra_git_version = "" ra_git_version = ""
try:
os_version = (
subprocess.run(
["uname", "--kernel-name", "--kernel-release", "--machine"],
capture_output=True,
check=True,
)
.stdout.decode("utf-8")
.strip()
)
except subprocess.CalledProcessError as error:
logging.warning(SHELL_ERROR, " ".join(error.cmd), error.stderr.decode("utf-8"))
os_version = "Unknown OS"
PROC_MODEL_PATH = "/proc/device-tree/model" PROC_MODEL_PATH = "/proc/device-tree/model"
SYS_VENDOR_PATH = "/sys/devices/virtual/dmi/id/sys_vendor" SYS_VENDOR_PATH = "/sys/devices/virtual/dmi/id/sys_vendor"
SYS_PROD_PATH = "/sys/devices/virtual/dmi/id/product_name" SYS_PROD_PATH = "/sys/devices/virtual/dmi/id/product_name"
@ -77,7 +64,11 @@ class SysCmds:
else: else:
hardware = "Unknown Device" hardware = "Unknown Device"
return {"git": ra_git_version, "env": f"{hardware}, {os_version}" } env = uname()
return {
"git": ra_git_version,
"env": f"{hardware}, {env.system} {env.release} {env.machine}",
}
@staticmethod @staticmethod
def running_proc(daemon): def running_proc(daemon):
@ -172,6 +163,42 @@ class SysCmds:
sock.close() sock.close()
return ip_addr, host return ip_addr, host
@staticmethod
def get_pretty_host():
"""
Returns either the pretty hostname if set, or the regular hostname as fallback.
"""
try:
process = run(
["hostnamectl", "status", "--pretty"],
capture_output=True,
check=True,
)
pretty_hostname = process.stdout.decode("utf-8").rstrip()
if pretty_hostname:
return pretty_hostname
except CalledProcessError as error:
logging.error(str(error))
return gethostname()
@staticmethod
def set_pretty_host(name):
"""
Set the pretty hostname for the system
"""
try:
process = run(
["sudo", "hostnamectl", "set-hostname", "--pretty", name],
capture_output=False,
check=True,
)
except CalledProcessError as error:
logging.error(str(error))
return False
return True
@staticmethod @staticmethod
def get_logs(lines, scope): def get_logs(lines, scope):
""" """

21
python/web/mock/bin/hostnamectl Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
TMP_FILE="/tmp/hostnamectl_pretty.tmp"
if [[ "$1" == "set-hostname" && "$2" == "--pretty" ]]; then
if [[ -z "$3" ]]; then
rm "$TMP_FILE" 2>/dev/null || true
else
echo "$3" > $TMP_FILE
fi
exit 0
fi
if [[ "$1" == "status" ]]; then
cat "$TMP_FILE" 2>/dev/null
exit 0
fi
echo "Mock does not recognize: $0 $@"
exit 1

View File

@ -208,12 +208,12 @@ select {
*/ */
div.header { div.header {
display: flex; display: flex;
align-items: center;
} }
div.header div.title { div.header div.title {
order: 1; order: 1;
text-align: left; text-align: left;
flex-grow: 1;
} }
div.header div.title h1 { div.header div.title h1 {
@ -227,7 +227,18 @@ div.header div.title a {
} }
div.header div.hostname { div.header div.hostname {
display: none; color: #ccc;
padding: 0 0.5rem;
order: 2;
flex-grow: 1;
}
div.header div.hostname span {
display: inline-block;
border: 1px solid #ccc;
border-radius: 1rem;
padding: 0.125rem 0.5rem;
font-size: 0.75rem;
} }
div.header div.login-status { div.header div.login-status {
@ -253,9 +264,10 @@ div.header div.authentication-disabled {
padding: 0 0.5rem; padding: 0 0.5rem;
} }
@media (max-width: 820px) { @media (max-width: 900px) {
div.header { div.header {
min-height: 3.5rem; /* Safari 14 iOS and iPad OS */ min-height: 3.5rem; /* Safari 14 iOS and iPad OS */
background: var(--dark);
} }
body:not(.logged-in) div.header { body:not(.logged-in) div.header {
@ -263,10 +275,6 @@ div.header div.authentication-disabled {
min-height: 8.875rem; /* Safari 14 iOS and iPad OS */ min-height: 8.875rem; /* Safari 14 iOS and iPad OS */
} }
div.header div.title {
background: var(--dark);
}
div.header div.title a { div.header div.title a {
display: block; display: block;
background: url("/static/logo.png") no-repeat; background: url("/static/logo.png") no-repeat;
@ -326,7 +334,7 @@ div.header div.authentication-disabled {
} }
} }
@media (min-width: 821px) { @media (min-width: 901px) {
div.header { div.header {
background: var(--dark); background: var(--dark);
align-items: center; align-items: center;
@ -509,7 +517,7 @@ section > details ul {
border-radius: 0.5rem; border-radius: 0.5rem;
} }
@media (max-width: 820px) { @media (max-width: 900px) {
section > details summary { section > details summary {
font-size: 0.9rem; font-size: 0.9rem;
} }
@ -578,7 +586,7 @@ table#attached-devices tr.reserved td {
background-color: #ffe9e9; background-color: #ffe9e9;
} }
@media (max-width: 820px) { @media (max-width: 900px) {
table#attached-devices th.product, table#attached-devices th.product,
table#attached-devices td.product { table#attached-devices td.product {
display: none; display: none;
@ -597,7 +605,7 @@ table#attached-devices tr.reserved td {
} }
} }
@media (min-width: 821px) { @media (min-width: 901px) {
section#current-config form#config-actions { section#current-config form#config-actions {
float: left; float: left;
height: 2.75rem; height: 2.75rem;
@ -670,7 +678,7 @@ section#files p {
margin-top: 1rem; margin-top: 1rem;
} }
@media (max-width: 820px) { @media (max-width: 900px) {
section#files table#images tr th:nth-child(2), section#files table#images tr th:nth-child(2),
section#files table#images tr td:nth-child(2) { section#files table#images tr td:nth-child(2) {
display: none; display: none;
@ -684,7 +692,7 @@ section#files p {
} }
} }
@media (min-width: 821px) { @media (min-width: 901px) {
section#files table#images form.file-copy input[type="submit"], section#files table#images form.file-copy input[type="submit"],
section#files table#images form.file-rename input[type="submit"], section#files table#images form.file-rename input[type="submit"],
section#files table#images form.file-delete input[type="submit"], section#files table#images form.file-delete input[type="submit"],
@ -740,7 +748,7 @@ section#attach-devices form {
display: block; display: block;
} }
@media (max-width: 820px) { @media (max-width: 900px) {
section#attach-devices table tr th:nth-child(2), section#attach-devices table tr th:nth-child(2),
section#attach-devices table tr td:nth-child(2) { section#attach-devices table tr td:nth-child(2) {
display: none; display: none;
@ -779,8 +787,12 @@ section#logging div:first-of-type {
Index > Section: System Index > Section: System
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*/ */
@media (min-width: 821px) { section#system div.power-control {
section#system input[type="submit"] { margin-top: 1rem;
}
@media (min-width: 901px) {
section#system div.power-control input[type="submit"] {
background: var(--danger); background: var(--danger);
border-color: var(--danger); border-color: var(--danger);
color: #fff; color: #fff;

View File

@ -77,8 +77,7 @@
</div> </div>
<div class="hostname"> <div class="hostname">
<span>{{ _("IP") }}: {{ env["ip_addr"] }}</span> <span>{{ env['system_name'] }}</span>
<span>{{ _("Hostname") }}: {{ env["host"] }}</span>
</div> </div>
</div> </div>
@ -126,10 +125,13 @@
{% endif %} {% endif %}
</div> </div>
<div> <div>
{{ _("RaSCSI Reloaded version: ") }}<b>{{ env["version"] }} <a href="https://github.com/akuker/RASCSI/commit/{{ env["running_env"]["git"] }}" target="_blank">{{ env["running_env"]["git"][:7] }}</a></b> {{ _("RaSCSI Reloaded version:") }} <b>{{ env["version"] }} <a href="https://github.com/akuker/RASCSI/commit/{{ env["running_env"]["git"] }}" target="_blank">{{ env["running_env"]["git"][:7] }}</a></b>
</div> </div>
<div> <div>
{{ _("Hardware and OS: ") }}{{ env["running_env"]["env"] }} {{ _("Hardware and OS:") }} {{ env["running_env"]["env"] }}
</div>
<div>
{{ _("Network Address:") }} {{ env["host"] }} ({{ env["ip_addr"] }})
</div> </div>
</div> </div>
</body> </body>

View File

@ -726,16 +726,30 @@
{{ _("System Operations") }} {{ _("System Operations") }}
</summary> </summary>
<ul> <ul>
<li>{{ _("For System Name we are using the high-level \"pretty\" hostname.") }}</li>
<li>{{ _("IMPORTANT: Always shut down the system before turning off the power. Failing to do so may lead to data loss.") }}</li> <li>{{ _("IMPORTANT: Always shut down the system before turning off the power. Failing to do so may lead to data loss.") }}</li>
</ul> </ul>
</details> </details>
<div>
<form action="/sys/rename" method="post">
<label for="system_name">{{ _("System Name:") }}</label>
<input name="system_name" id="system_name" type="text" maxlength=120 required>
<input type="submit" value="{{ _("Rename") }}">
</form>
<form action="/sys/rename" method="post">
<input name="system_name" type="hidden" value="">
<input type="submit" value="{{ _("Reset") }}">
</form>
</div>
<div class="power-control">
<form action="/sys/reboot" method="post" onclick="if (confirm('{{ _("Reboot the System?") }}')) shutdownNotify('{{ _("Rebooting the system...") }}'); else event.preventDefault();"> <form action="/sys/reboot" method="post" onclick="if (confirm('{{ _("Reboot the System?") }}')) shutdownNotify('{{ _("Rebooting the system...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Reboot System") }}"> <input type="submit" value="{{ _("Reboot System") }}">
</form> </form>
<form action="/sys/shutdown" method="post" onclick="if (confirm('{{ _("Shut down the System?") }}')) shutdownNotify('{{ _("Shutting down the system...") }}'); else event.preventDefault();"> <form action="/sys/shutdown" method="post" onclick="if (confirm('{{ _("Shut Down the System?") }}')) shutdownNotify('{{ _("Shutting down the system...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Shut Down System") }}"> <input type="submit" value="{{ _("Shut Down System") }}">
</form> </form>
</div>
</section> </section>
<hr/> <hr/>

View File

@ -94,6 +94,7 @@ def get_env_info():
"logged_in": username and auth_active(AUTH_GROUP)["status"], "logged_in": username and auth_active(AUTH_GROUP)["status"],
"ip_addr": ip_addr, "ip_addr": ip_addr,
"host": host, "host": host,
"system_name": sys_cmd.get_pretty_host(),
"free_disk_space": int(sys_cmd.disk_space()["free"] / 1024 / 1024), "free_disk_space": int(sys_cmd.disk_space()["free"] / 1024 / 1024),
"locale": get_locale(), "locale": get_locale(),
"version": server_info["version"], "version": server_info["version"],
@ -797,6 +798,25 @@ def release_id():
return response(error=True, message=process["msg"]) return response(error=True, message=process["msg"])
@APP.route("/sys/rename", methods=["POST"])
@login_required
def rename_system():
"""
Changes the hostname of the system
"""
name = str(request.form.get("system_name"))
max_length = 120
if len(name) <= max_length:
process = sys_cmd.set_pretty_host(name)
if process:
if name:
return response(message=_("System name changed to '%(name)s'.", name=name))
return response(message=_("System name reset to default."))
return response(error=True, message=_("Failed to change system name."))
@APP.route("/sys/reboot", methods=["POST"]) @APP.route("/sys/reboot", methods=["POST"])
@login_required @login_required
def restart(): def restart():

View File

@ -196,3 +196,49 @@ def test_set_theme_via_query_string(http_client, theme):
assert response.status_code == 200 assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'." assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'."
# route("/sys/rename", methods=["POST"])
def test_rename_system(env, http_client):
new_name = "SYSTEM NAME TEST"
response = http_client.get("/env")
response_data = response.json()
old_name = response_data["data"]["system_name"]
response = http_client.post(
"/sys/rename",
data={
"system_name": new_name,
},
)
response_data = response.json()
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == f"System name changed to '{new_name}'."
response = http_client.get("/env")
response_data = response.json()
assert response_data["data"]["system_name"] == new_name
response = http_client.post(
"/sys/rename",
data={
"system_name": old_name,
},
)
response_data = response.json()
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == f"System name changed to '{old_name}'."
response = http_client.get("/env")
response_data = response.json()
assert response_data["data"]["system_name"] == old_name