mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-08 04:27:12 +03:00
Firmware update UI revamp with support for local proxy
This commit is contained in:
@@ -26,11 +26,11 @@
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="nav navbar-nav mr-auto">
|
||||
<li class="nav-item"><a class="nav-link active" data-toggle="tab" href="#tab-wifi">WiFi</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-syslog">Status<span
|
||||
<li class="nav-item omsg"><a class="nav-link" data-toggle="tab" href="#tab-syslog">Status<span
|
||||
class="badge badge-pill badge-success" id="msgcnt"></span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-cfg-audio">Audio</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-cfg-syst">System</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-cfg-hw">Hardware</a></li>
|
||||
<li class="nav-item orec"><a class="nav-link" data-toggle="tab" href="#tab-cfg-audio">Audio</a></li>
|
||||
<li class="nav-item orec"><a class="nav-link" data-toggle="tab" href="#tab-cfg-syst">System</a></li>
|
||||
<li class="nav-item orec"><a class="nav-link" data-toggle="tab" href="#tab-cfg-hw">Hardware</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-cfg-fw">Updates</a></li>
|
||||
<div class="dropdown-divider"></div>
|
||||
<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-nvs">NVS Editor</a></li>
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<main role="main" class="flex-grow mt-1 mb-5" id="content">
|
||||
<main role="main" class="flex-grow mt-1 mb-12" style="margin-bottom: 7rem;" id="content">
|
||||
<!-- Button trigger modal -->
|
||||
|
||||
<!-- Modal -->
|
||||
@@ -185,10 +185,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card text-white mb-3 recovery_element" style="display: none;">
|
||||
<div class="card text-white mb-3" >
|
||||
<div class="card-header">Local Firmware Upload</div>
|
||||
<div class="card-body">
|
||||
<div id="uploaddiv" class="recovery_element form-group row">
|
||||
<div id="uploaddiv" class="form-group row">
|
||||
<label for="flashfilename" class="col-auto col-form-label">Local File</label>
|
||||
<div class="col">
|
||||
<input type="file" class="form-control-file" id="flashfilename" aria-describedby="fileHelp">
|
||||
@@ -217,12 +217,8 @@
|
||||
<tbody id="nvsTable">
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="buttons">
|
||||
<div id="boot-div">
|
||||
<form id="reboot-form" action="/reboot.json" method="post" target="dummyframe">
|
||||
<button id="reboot-button" type="submit" class="btn btn-primary">Reboot</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between ">
|
||||
<button button id="reboot-button" class="btn btn-primary" type="submit" onclick="handleReboot('reboot');" >Reboot</button>
|
||||
<input id="save-nvs" type="button" class="btn btn-success" value="Commit">
|
||||
<input id="save-as-nvs" type="button" class="btn btn-success" value="Download config">
|
||||
<input id="load-nvs" type="button" class="btn btn-success" value="Load File">
|
||||
@@ -411,7 +407,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-primary mb-3">
|
||||
<div class="card border-primary mb-3" id="pins" style="display: none;">
|
||||
<div class="card-header">Pin Assignments</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover">
|
||||
|
||||
@@ -112,14 +112,16 @@ const flash_status_codes = {
|
||||
REBOOT_TO_RECOVERY: 2,
|
||||
SET_FWURL: 5,
|
||||
FLASHING: 6,
|
||||
DONE: 7
|
||||
DONE: 7,
|
||||
UPLOADING: 8,
|
||||
ERROR: 9
|
||||
};
|
||||
let flash_state=flash_status_codes.FLASH_NONE;
|
||||
let flash_ota_dsc='';
|
||||
let flash_ota_pct=0;
|
||||
let older_recovery=false;
|
||||
function isFlashExecuting(data){
|
||||
return data.ota_dsc!='' || data.ota_pct>0;
|
||||
return (flash_state!=flash_status_codes.UPLOADING ) && (data.ota_dsc!='' || data.ota_pct>0);
|
||||
}
|
||||
function post_config(data){
|
||||
let confPayload={
|
||||
@@ -140,16 +142,21 @@ function process_ota_event(data){
|
||||
if(data.ota_dsc){
|
||||
flash_ota_dsc=data.ota_dsc;
|
||||
}
|
||||
if(data.ota_pct){
|
||||
if( data.ota_pct != undefined){
|
||||
flash_ota_pct=data.ota_pct;
|
||||
}
|
||||
if(isFlashExecuting(data)){
|
||||
|
||||
if(flash_state==flash_status_codes.ERROR){
|
||||
return;
|
||||
}
|
||||
else if(isFlashExecuting(data)){
|
||||
flash_state=flash_status_codes.FLASHING;
|
||||
}
|
||||
if(flash_state==flash_status_codes.FLASHING){
|
||||
else if(flash_state==flash_status_codes.FLASHING ){
|
||||
if(flash_ota_pct ==100){
|
||||
// we were processing OTA, and we've reached 100%
|
||||
flash_state=flash_status_codes.DONE;
|
||||
$('#flashfilename').val('');
|
||||
}
|
||||
else if(flash_ota_pct<0 && older_recovery){
|
||||
// we were processing OTA on an older recovery and we missed the
|
||||
@@ -161,11 +168,48 @@ function process_ota_event(data){
|
||||
flash_state=flash_status_codes.DONE;
|
||||
}
|
||||
}
|
||||
else if(flash_state ==flash_status_codes.UPLOADING){
|
||||
if(flash_ota_pct ==100){
|
||||
// we were processing OTA, and we've reached 100%
|
||||
// reset the progress bar
|
||||
flash_ota_pct = 0;
|
||||
flash_state=flash_status_codes.FLASHING;
|
||||
}
|
||||
}
|
||||
}
|
||||
function set_ota_error(message){
|
||||
flash_state=flash_status_codes.ERROR;
|
||||
handle_flash_state({
|
||||
ota_pct: 0,
|
||||
ota_dsc: message,
|
||||
event: flash_events.SET_ERROR
|
||||
});
|
||||
}
|
||||
function show_update_dialog(){
|
||||
$('#otadiv').modal();
|
||||
if (flash_ota_pct >= 0) {
|
||||
update_progress();
|
||||
}
|
||||
if (flash_ota_dsc !== '') {
|
||||
$('span#flash-status').html(flash_ota_dsc);
|
||||
}
|
||||
}
|
||||
const flash_events={
|
||||
SET_ERROR: function(data){
|
||||
if(data.ota_dsc){
|
||||
flash_ota_dsc=data.ota_dsc;
|
||||
}
|
||||
else {
|
||||
flash_ota_dsc = 'Error';
|
||||
}
|
||||
flash_ota_pct=data.ota_pct??0;
|
||||
$('#fwProgressLabel').parent().addClass('bg-danger');
|
||||
update_progress();
|
||||
show_update_dialog();
|
||||
},
|
||||
START_OTA : function() {
|
||||
if (flash_state == flash_status_codes.NONE || flash_state == undefined) {
|
||||
console.log('Starting OTA process');
|
||||
if (flash_state == flash_status_codes.NONE || flash_state == flash_status_codes.ERROR || flash_state == undefined) {
|
||||
$('#fwProgressLabel').parent().removeClass('bg-danger');
|
||||
flash_state=flash_status_codes.REBOOT_TO_RECOVERY;
|
||||
if(!recovery){
|
||||
flash_ota_dsc = 'Starting recovery mode...';
|
||||
@@ -181,13 +225,19 @@ const flash_events={
|
||||
cache: false,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
data: JSON.stringify(data),
|
||||
error: handleExceptionResponse,
|
||||
error: function(xhr, _ajaxOptions, thrownError){
|
||||
set_ota_error(`Unexpected error while trying to restart to recovery. (status=${xhr.status??''}, error=${thrownError??''} ) `);
|
||||
},
|
||||
complete: function(response) {
|
||||
console.log(response.responseText);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
flash_ota_dsc='Starting Update';
|
||||
}
|
||||
show_update_dialog();
|
||||
|
||||
}
|
||||
else {
|
||||
console.warn('Unexpected status while starting flashing');
|
||||
@@ -195,17 +245,49 @@ const flash_events={
|
||||
},
|
||||
FOUND_RECOVERY: function(data) {
|
||||
console.log(JSON.stringify(data));
|
||||
const url=$('#fw-url-input').val();
|
||||
if(flash_state == flash_status_codes.REBOOT_TO_RECOVERY){
|
||||
flash_ota_dsc = 'Recovery mode found. Flashing device.';
|
||||
flash_state= flash_status_codes.SET_FWURL;
|
||||
let confData= { fwurl: {
|
||||
value: $('#fw-url-input').val(),
|
||||
type: 33,
|
||||
}
|
||||
};
|
||||
post_config(confData);
|
||||
const fileInput = $('#flashfilename')[0].files;
|
||||
if (fileInput.length > 0) {
|
||||
flash_ota_dsc = 'Sending file to device.';
|
||||
flash_state= flash_status_codes.UPLOADING;
|
||||
const uploadPath = '/flash.json';
|
||||
const xhttp = new XMLHttpRequest();
|
||||
// xhrObj.upload.addEventListener("loadstart", loadStartFunction, false);
|
||||
xhttp.upload.addEventListener("progress", progressFunction, false);
|
||||
//xhrObj.upload.addEventListener("load", transferCompleteFunction, false);
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (xhttp.readyState === 4) {
|
||||
if(xhttp.status === 0 || xhttp.status === 404) {
|
||||
set_ota_error(`Upload Failed. Recovery version might not support uploading. Please use web update instead.`);
|
||||
$('#flashfilename').val('');
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open('POST', uploadPath, true);
|
||||
xhttp.send(fileInput[0] );
|
||||
}
|
||||
else if(url==''){
|
||||
flash_state= flash_status_codes.NONE;
|
||||
}
|
||||
else {
|
||||
flash_ota_dsc = 'Saving firmware URL location.';
|
||||
flash_state= flash_status_codes.SET_FWURL;
|
||||
let confData= { fwurl: {
|
||||
value: $('#fw-url-input').val(),
|
||||
type: 33,
|
||||
}
|
||||
};
|
||||
post_config(confData);
|
||||
}
|
||||
show_update_dialog();
|
||||
}
|
||||
},
|
||||
PROCESS_OTA_UPLOAD: function(data){
|
||||
flash_state= flash_status_codes.UPLOADING;
|
||||
process_ota_event(data);
|
||||
show_update_dialog();
|
||||
},
|
||||
PROCESS_OTA_STATUS: function(data){
|
||||
if(data.ota_pct>0){
|
||||
older_recovery = true;
|
||||
@@ -218,22 +300,33 @@ const flash_events={
|
||||
flash_state=flash_status_codes.NONE;
|
||||
$('#rTable tr.release').removeClass('table-success table-warning');
|
||||
$('#fw-url-input').val('');
|
||||
$('#otadiv').modal('hide');
|
||||
}
|
||||
|
||||
else {
|
||||
process_ota_event(data);
|
||||
}
|
||||
|
||||
if(flash_state && (flash_state >flash_status_codes.NONE && flash_ota_pct>=0) ) {
|
||||
show_update_dialog();
|
||||
}
|
||||
}
|
||||
},
|
||||
PROCESS_OTA: function(data) {
|
||||
process_ota_event(data);
|
||||
if(flash_state && (flash_state >flash_status_codes.NONE && flash_ota_pct>=0) ) {
|
||||
show_update_dialog();
|
||||
}
|
||||
}
|
||||
};
|
||||
window.hideSurrounding = function(obj){
|
||||
$(obj).parent().parent().hide();
|
||||
}
|
||||
function update_progress(){
|
||||
$('.progress-bar')
|
||||
.css('width', flash_ota_pct + '%')
|
||||
.attr('aria-valuenow', flash_ota_pct)
|
||||
.text(flash_ota_pct+'%')
|
||||
$('.progress-bar').html((flash_state==flash_status_codes.DONE?100:flash_ota_pct) + '%');
|
||||
|
||||
}
|
||||
function handle_flash_state(data) {
|
||||
if(data.event) {
|
||||
data.event(data);
|
||||
@@ -243,37 +336,33 @@ function handle_flash_state(data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(flash_state && flash_state >flash_status_codes.NONE && flash_ota_pct>=0) {
|
||||
|
||||
$('#otadiv').modal();
|
||||
if (flash_ota_pct !== 0) {
|
||||
$('.progress-bar')
|
||||
.css('width', flash_ota_pct + '%')
|
||||
.attr('aria-valuenow', flash_ota_pct)
|
||||
.text(flash_ota_pct+'%')
|
||||
$('.progress-bar').html((flash_state==flash_status_codes.DONE?100:flash_ota_pct) + '%');
|
||||
}
|
||||
if (flash_ota_dsc !== '') {
|
||||
$('span#flash-status').html(flash_ota_dsc);
|
||||
}
|
||||
}
|
||||
else {
|
||||
flash_ota_pct=0;
|
||||
flash_ota_dsc='';
|
||||
}
|
||||
}
|
||||
window.hFlash = function(){
|
||||
// reset file upload selection if any;
|
||||
$('#flashfilename').val('');
|
||||
handle_flash_state({ event: flash_events.START_OTA, url: $('#fw-url-input').val() });
|
||||
}
|
||||
window.handleReboot = function(link){
|
||||
|
||||
if(link=='reboot_ota'){
|
||||
$('#reboot_ota_nav').removeClass('active'); delayReboot(500,'', 'reboot_ota');
|
||||
$('#reboot_ota_nav').removeClass('active').prop("disabled",true); delayReboot(500,'', 'reboot_ota');
|
||||
}
|
||||
else {
|
||||
$('#reboot_nav').removeClass('active'); delayReboot(500,'',link);
|
||||
}
|
||||
}
|
||||
|
||||
function progressFunction(evt){
|
||||
// if (evt.lengthComputable) {
|
||||
// progressBar.max = evt.total;
|
||||
// progressBar.value = evt.loaded;
|
||||
// percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";
|
||||
// }
|
||||
handle_flash_state({
|
||||
ota_pct: ( Math.round(evt.loaded / evt.total * 100)),
|
||||
ota_dsc: ('Uploading file to device'),
|
||||
event: flash_events.PROCESS_OTA_UPLOAD
|
||||
});
|
||||
}
|
||||
function handlebtstate(data) {
|
||||
let icon = '';
|
||||
let tt = '';
|
||||
@@ -373,6 +462,7 @@ let LastCommandsState = null;
|
||||
var output = '';
|
||||
let hostName = '';
|
||||
let versionName='Squeezelite-ESP32';
|
||||
let prevmessage='';
|
||||
let project_name=versionName;
|
||||
let platform_name=versionName;
|
||||
let btSinkNamesOptSel='#cfg-audio-bt_source-sink_name';
|
||||
@@ -515,6 +605,7 @@ function delayReboot(duration, cmdname, ota = 'reboot') {
|
||||
showLocalMessage('System is rebooting.\n', 'MESSAGING_WARNING');
|
||||
}
|
||||
console.log('now triggering reboot');
|
||||
$("button[onclick*='handleReboot']").addClass('rebooting');
|
||||
$.ajax({
|
||||
url: data.url,
|
||||
dataType: 'text',
|
||||
@@ -619,7 +710,13 @@ window.handleDisconnect = function(){
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function setPlatformFilter(val){
|
||||
if($('.upf').filter(function(){ return $(this).text().toUpperCase()===val.toUpperCase()}).length>0){
|
||||
$('#splf').val(val).trigger('input');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
window.handleConnect = function(){
|
||||
ConnectingToSSID.ssid = $('#manual_ssid').val();
|
||||
ConnectingToSSID.pwd = $('#manual_pwd').val();
|
||||
@@ -777,40 +874,15 @@ $(document).ready(function() {
|
||||
$('#save-nvs').on('click', function() {
|
||||
post_config(getConfigJson(false));
|
||||
});
|
||||
|
||||
$('#fwUpload').on('click', function() {
|
||||
const uploadPath = '/flash.json';
|
||||
|
||||
if (!recovery) {
|
||||
$('#flash-status').text('Rebooting to recovery. Please try again');
|
||||
window.handleReboot(false);
|
||||
}
|
||||
|
||||
const fileInput = document.getElementById('flashfilename').files;
|
||||
if (fileInput.length === 0) {
|
||||
alert('No file selected!');
|
||||
} else {
|
||||
const file = fileInput[0];
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (xhttp.readyState === 4) {
|
||||
if (xhttp.status === 200) {
|
||||
showLocalMessage(xhttp.responseText, 'MESSAGING_INFO');
|
||||
} else if (xhttp.status === 0) {
|
||||
showLocalMessage(
|
||||
'Upload connection was closed abruptly!',
|
||||
'MESSAGING_ERROR'
|
||||
);
|
||||
} else {
|
||||
showLocalMessage(
|
||||
xhttp.status + ' Error!\n' + xhttp.responseText,
|
||||
'MESSAGING_ERROR'
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open('POST', uploadPath, true);
|
||||
xhttp.send(file);
|
||||
handle_flash_state({ event: flash_events.START_OTA, file: fileInput[0] });
|
||||
}
|
||||
|
||||
});
|
||||
$('[name=output-tmpl]').on('click', function() {
|
||||
handleTemplateTypeRadio(this.id);
|
||||
@@ -874,13 +946,9 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
$('#searchfw').css('display', 'inline');
|
||||
if(platform_name!=='' && $('.upf').filter(function(){ return $(this).text().toUpperCase()===platform_name.toUpperCase()}).length>0){
|
||||
$('#splf').val(platform_name).trigger('input');
|
||||
if(!setPlatformFilter(platform_name)){
|
||||
setPlatformFilter(project_name)
|
||||
}
|
||||
else if($('.upf').filter(function(){ return $(this).text().toUpperCase()===project_name.toUpperCase()}).length>0){
|
||||
$('#splf').val(project_name).trigger('input');
|
||||
}
|
||||
|
||||
$('#rTable tr.release').on('click', function() {
|
||||
var url=this.attributes['fwurl'].value;
|
||||
if (lmsBaseUrl) {
|
||||
@@ -1221,7 +1289,16 @@ function getMessages() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).fail(handleExceptionResponse);
|
||||
}).fail(function(xhr, ajaxOptions, thrownError){
|
||||
if(xhr.status==404){
|
||||
$('.orec').hide(); // system commands won't be available either
|
||||
}
|
||||
else {
|
||||
handleExceptionResponse(xhr, ajaxOptions, thrownError);
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
Minstk is minimum stack space left
|
||||
@@ -1408,6 +1485,12 @@ function checkStatus() {
|
||||
} else {
|
||||
$('#battery').hide();
|
||||
}
|
||||
if((data.message??'')!='' && prevmessage != data.message){
|
||||
// supporting older recovery firmwares - messages will come from the status.json structure
|
||||
prevmessage = data.message;
|
||||
showLocalMessage(data.message, 'MESSAGING_INFO')
|
||||
}
|
||||
$("button[onclick*='handleReboot']").removeClass('rebooting');
|
||||
|
||||
if (typeof lmsBaseUrl == "undefined" || data.lms_ip != prevLMSIP && data.lms_ip && data.lms_port) {
|
||||
const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;
|
||||
@@ -1489,9 +1572,29 @@ window.runCommand = function(button, reboot) {
|
||||
cache: false,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
data: JSON.stringify(data),
|
||||
error: handleExceptionResponse,
|
||||
complete: function(response) {
|
||||
error: function(xhr, _ajaxOptions, thrownError){
|
||||
var cmd=JSON.parse(this.data ).command;
|
||||
if(xhr.status==404){
|
||||
showCmdMessage(
|
||||
cmd.substr(0,cmd.indexOf(' ')),
|
||||
'MESSAGING_ERROR',
|
||||
`${recovery?'Limited recovery mode active. Unsupported action ':'Unexpected error while processing command'}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
else {
|
||||
handleExceptionResponse(xhr, _ajaxOptions, thrownError);
|
||||
showCmdMessage(
|
||||
cmd.substr(0,cmd.indexOf(' ')-1),
|
||||
'MESSAGING_ERROR',
|
||||
`Unexpected error ${(thrownError !== '')?thrownError:'with return status = '+xhr.status}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
success: function(response) {
|
||||
// var returnedResponse = JSON.parse(response.responseText);
|
||||
$('.orec').show();
|
||||
console.log(response.responseText);
|
||||
if (
|
||||
response.responseText &&
|
||||
@@ -1509,6 +1612,7 @@ function getLongOps(data, name, longopts){
|
||||
function getCommands() {
|
||||
$.getJSON('/commands.json', function(data) {
|
||||
console.log(data);
|
||||
$('.orec').show();
|
||||
data.commands.forEach(function(command) {
|
||||
if ($('#flds-' + command.name).length === 0) {
|
||||
const cmdParts = command.name.split('-');
|
||||
@@ -1664,7 +1768,13 @@ function getCommands() {
|
||||
}
|
||||
});
|
||||
}).fail(function(xhr, ajaxOptions, thrownError) {
|
||||
handleExceptionResponse(xhr, ajaxOptions, thrownError);
|
||||
if(xhr.status==404){
|
||||
$('.orec').hide();
|
||||
}
|
||||
else {
|
||||
handleExceptionResponse(xhr, ajaxOptions, thrownError);
|
||||
}
|
||||
|
||||
$('#commands-list').empty();
|
||||
blockAjax = false;
|
||||
});
|
||||
@@ -1725,6 +1835,7 @@ function getConfig() {
|
||||
"<tr><td><input type='text' class='form-control' id='nvs-new-key' placeholder='new key'></td><td><input type='text' class='form-control' id='nvs-new-value' placeholder='new value' nvs_type=33 ></td></tr>"
|
||||
);
|
||||
if (entries.gpio) {
|
||||
$('#pins').show();
|
||||
$('tbody#gpiotable tr').remove();
|
||||
entries.gpio.forEach(function(gpioEntry) {
|
||||
$('tbody#gpiotable').append(
|
||||
@@ -1742,6 +1853,9 @@ function getConfig() {
|
||||
);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#pins').hide();
|
||||
}
|
||||
}).fail(function(xhr, ajaxOptions, thrownError) {
|
||||
handleExceptionResponse(xhr, ajaxOptions, thrownError);
|
||||
blockAjax = false;
|
||||
|
||||
@@ -13,6 +13,9 @@ body {
|
||||
tr.hide {
|
||||
display: none;
|
||||
}
|
||||
.rebooting {
|
||||
display: none;
|
||||
}
|
||||
/* body {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
|
||||
Reference in New Issue
Block a user