system: Redesign system page
All css files under systemd/ directory where changed to less for compatibility with the new system-global.less file. Fixes #13121 Closes #13186
This commit is contained in:
parent
b3d6873db9
commit
f8c0966a6d
|
@ -216,7 +216,7 @@ tr.security.listing-ct-item {
|
|||
margin: 10ex auto 0;
|
||||
}
|
||||
|
||||
/* stolen from pkg/systemd/host.css */
|
||||
/* stolen from pkg/systemd/overview.less */
|
||||
.content-header-extra {
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
|
|
@ -571,6 +571,7 @@ function setup() {
|
|||
var link = $("<a tabindex='0'>");
|
||||
element.append(link);
|
||||
var hostname_link = $("#system_information_hostname_button");
|
||||
var hostname_tooltip = $("#system_information_hostname_tooltip");
|
||||
|
||||
var realmd = null;
|
||||
var realms = null;
|
||||
|
@ -600,10 +601,13 @@ function setup() {
|
|||
if (!joined || !joined.length) {
|
||||
text = _("Join Domain");
|
||||
hostname_link.removeAttr('disabled');
|
||||
hostname_tooltip.removeAttr('title');
|
||||
hostname_tooltip.removeAttr('data-original-title');
|
||||
} else {
|
||||
text = joined.map(function(x) { return x.Name }).join(", ");
|
||||
hostname_link.attr('disabled', 'disabled');
|
||||
hostname_link.attr('title', _("Host name should not be changed in a domain")).tooltip('fixTitle');
|
||||
hostname_tooltip.attr('title', _("Host name should not be changed in a domain"))
|
||||
.tooltip('fixTitle');
|
||||
}
|
||||
link.text(text);
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import $ from "jquery";
|
||||
import cockpit from "cockpit";
|
||||
|
||||
/*
|
||||
* INITIALIZATION AND NAVIGATION
|
||||
*
|
||||
* The code above still uses the legacy 'Page' abstraction for both
|
||||
* pages and dialogs, and expects page.setup, page.enter, page.show,
|
||||
* and page.leave to be called at the right times.
|
||||
*
|
||||
* We cater to this with a little compatability shim consisting of
|
||||
* 'dialog_setup', 'page_show', and 'page_hide'.
|
||||
*/
|
||||
|
||||
export function page_show(p, arg) {
|
||||
if (!p._entered_) {
|
||||
p.enter(arg);
|
||||
}
|
||||
p._entered_ = true;
|
||||
$('#' + p.id)
|
||||
.show()
|
||||
.removeAttr("hidden");
|
||||
p.show();
|
||||
}
|
||||
|
||||
export function set_page_link(element_sel, page, text) {
|
||||
if (cockpit.manifests[page]) {
|
||||
var link = document.createElement("a");
|
||||
link.innerHTML = text;
|
||||
link.tabIndex = 0;
|
||||
link.addEventListener("click", function() { cockpit.jump("/" + page) });
|
||||
$(element_sel).html(link);
|
||||
} else {
|
||||
$(element_sel).text(text);
|
||||
}
|
||||
}
|
||||
|
||||
export function dialog_setup(d) {
|
||||
d.setup();
|
||||
$('#' + d.id)
|
||||
.on('show.bs.modal', function(event) {
|
||||
if (event.target.id === d.id)
|
||||
d.enter();
|
||||
})
|
||||
.on('shown.bs.modal', function(event) {
|
||||
if (event.target.id === d.id)
|
||||
d.show();
|
||||
})
|
||||
.on('hidden.bs.modal', function(event) {
|
||||
if (event.target.id === d.id)
|
||||
d.leave();
|
||||
});
|
||||
}
|
||||
|
||||
export function page_hide(p) {
|
||||
$('#' + p.id).hide();
|
||||
}
|
|
@ -1,198 +0,0 @@
|
|||
@import "../lib/table.css";
|
||||
@import "../lib/cockpit-components-empty-state.css";
|
||||
|
||||
body {
|
||||
/* Work around a pesky scrollbar on the page, due to 100% height */
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.server-overview {
|
||||
display: grid;
|
||||
grid-gap: 0 2rem;
|
||||
/* By default, horizontally stack the grid elements */
|
||||
grid-template-areas: "motd" "info";
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 640px) {
|
||||
/* Lay out overview in a grid, if there's enough width */
|
||||
.server-overview {
|
||||
grid-template-areas: "motd motd";
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.server-overview > .info-table-ct-container {
|
||||
grid-area: info;
|
||||
}
|
||||
|
||||
.server-overview > .motd-box {
|
||||
grid-area: motd;
|
||||
}
|
||||
|
||||
.systime-inline form .pficon-close,
|
||||
.systime-inline form .fa-plus {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
padding: 4px;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.systime-inline .form-inline {
|
||||
background: #f4f4f4;
|
||||
border-width: 0 1px 1px 1px;
|
||||
border-style: solid;
|
||||
border-color: #bababa;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.systime-inline .form-inline:first-of-type {
|
||||
border-top: 1px solid #bababa;
|
||||
}
|
||||
|
||||
.systime-inline .form-control {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.systime-inline .form-group:first-of-type .form-control {
|
||||
margin: 0 4px 0 0;
|
||||
}
|
||||
|
||||
.systime-inline .form-group .form-control {
|
||||
width: 214px;
|
||||
}
|
||||
|
||||
/* Make sure error message don't overflow the dialog */
|
||||
|
||||
.realms-op-diagnostics {
|
||||
max-width: 550px;
|
||||
text-align: left;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.realms-op-wait-message {
|
||||
margin-left: 10px;
|
||||
float: left;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.realms-op-error .realms-op-more-diagnostics {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* leave some space between form and leave toggle */
|
||||
#realms-op-leave-toggle {
|
||||
font-weight: bold;
|
||||
line-height: 5rem;
|
||||
}
|
||||
|
||||
/* standard PF alerts have a wide margin */
|
||||
.realms-op-leave-only-row .pf-c-alert {
|
||||
padding-left: 2ex;
|
||||
}
|
||||
|
||||
.realms-op-leave-only-row .pf-c-alert button {
|
||||
margin: 1ex 0;
|
||||
}
|
||||
|
||||
/* Other styles */
|
||||
|
||||
.small-messages {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
#sich-note-1,
|
||||
#sich-note-2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#system_information_ssh_keys .list-group-item {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
#system_information_hardware_text,
|
||||
#system_information_os_text,
|
||||
#system-information-enable-pcp-link {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.system-information-updates > .fa {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
#system_machine_id {
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.cockpit-modal-md {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 960px) {
|
||||
.service-unit-data {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
#accordion-markup {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.nav-tabs-pf > li:first-child > a {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.nav li a {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.detail_table > tbody > tr > td, .detail_table > thead > tr > th {
|
||||
padding-right: 10px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#motd {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.reporting-table tr:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.reporting-table td:first-child {
|
||||
white-space: nowrap;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
table.reporting-table td:nth-child(2) {
|
||||
text-align: start;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
table.reporting-table td:nth-child(2) > .spinner {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td.report-column {
|
||||
flex-direction: row-reverse;
|
||||
}
|
|
@ -294,7 +294,7 @@ class HardwareInfo extends React.Component {
|
|||
<div className="page-ct container-fluid">
|
||||
<CPUSecurityMitigationsDialog show={this.state.showCpuSecurityDialog} onClose={ () => this.setState({ showCpuSecurityDialog: false }) } />
|
||||
<ol className="breadcrumb">
|
||||
<li><button role="link" className="link-button" onClick={ () => cockpit.jump("/system", cockpit.transport.host) }>{ _("System") }</button></li>
|
||||
<li><button role="link" className="link-button" onClick={ () => cockpit.jump("/system", cockpit.transport.host) }>{ _("Overview") }</button></li>
|
||||
<li className="active">{ _("Hardware Information") }</li>
|
||||
</ol>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "./system-global.css";
|
||||
@import "./system-global.less";
|
||||
@import "../lib/table.css";
|
||||
|
||||
/* show tbodys side by side on wide screens */
|
|
@ -1,422 +1,297 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title translate="yes">System</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="../base1/patternfly.css" rel="stylesheet">
|
||||
<link href="../shell/index.css" rel="stylesheet">
|
||||
<link href="system.css" rel="stylesheet">
|
||||
<script src="../base1/jquery.js"></script>
|
||||
<script src="../base1/cockpit.js"></script>
|
||||
<script src="../manifests.js"></script>
|
||||
<script src="../*/po.js"></script>
|
||||
<meta charset="utf-8">
|
||||
<title>Overview</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="../base1/patternfly.css">
|
||||
<link rel="stylesheet" href="overview.css">
|
||||
|
||||
<script type="text/javascript" src="../base1/cockpit.js"></script>
|
||||
<script type="text/javascript" src="../base1/jquery.js"></script>
|
||||
<!-- <script type="text/javascript" src="d3.js"></script> -->
|
||||
<script type="text/javascript" src="overview.js"></script>
|
||||
<script type="text/javascript" src="../*/po.js"></script>
|
||||
<script src="../manifests.js"></script>
|
||||
</head>
|
||||
<body class="pf-m-redhat-font" hidden>
|
||||
<body class="pf-m-redhat-font">
|
||||
<div id="overview"></div>
|
||||
|
||||
<script id="ssh-host-keys-tmpl" type="x-template/mustache">
|
||||
<div class="list-group dialog-list-ct">
|
||||
{{#keys}}
|
||||
<div class="list-group-item">
|
||||
<p>{{ title }}</p>
|
||||
{{#fps}}
|
||||
<small>{{.}}</small>
|
||||
{{/fps}}
|
||||
</div>
|
||||
{{/keys}}
|
||||
{{^keys}}
|
||||
<div class="list-group-item">
|
||||
<p translate="yes">No host keys found.</p>
|
||||
</div>
|
||||
{{/keys}}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="ntp-status-icon-tmpl" type="x-template/mustache">
|
||||
{{#Synched}}
|
||||
<span class="fa fa-lg fa-info-circle"></span>
|
||||
{{/Synched}}
|
||||
{{^Synched}}
|
||||
{{#Server}}
|
||||
<span class="spinner spinner-xs spinner-inline"></span>
|
||||
{{/Server}}
|
||||
{{^Server}}
|
||||
<span class="fa fa-lg fa-exclamation-circle"></span>
|
||||
{{/Server}}
|
||||
{{/Synched}}
|
||||
</script>
|
||||
|
||||
<script id="ntp-status-tmpl" type="x-template/mustache">
|
||||
{{#Synched}}
|
||||
{{#Server}}
|
||||
<div translate="yes">Synchronized with {{Server}}</div>
|
||||
{{/Server}}
|
||||
{{^Server}}
|
||||
<div translate="yes">Synchronized</div>
|
||||
{{/Server}}
|
||||
{{/Synched}}
|
||||
{{^Synched}}
|
||||
{{#Server}}
|
||||
<div translate="yes">Trying to synchronize with {{Server}}</div>
|
||||
{{/Server}}
|
||||
{{^Server}}
|
||||
<div translate="yes">Not synchronized</div>
|
||||
{{#service}}
|
||||
<a tabindex="0" data-goto-service="{{.}}" class="small-messages" translate>Log messages</a>
|
||||
{{/service}}
|
||||
{{/Server}}
|
||||
{{/Synched}}
|
||||
{{#SubStatus}}
|
||||
<div class="small-messages">{{SubStatus}}</div>
|
||||
{{/SubStatus}}
|
||||
</script>
|
||||
|
||||
<div id="server" class="container-fluid page-ct server-overview">
|
||||
<div id="motd-box" class="motd-box" hidden>
|
||||
<div class="pf-c-alert pf-m-info pf-m-inline" aria-label="Info alert">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fa fa-info-circle" aria-hidden="true"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title">
|
||||
<pre id="motd"></pre>
|
||||
</h4>
|
||||
<div class="pf-c-alert__action">
|
||||
<button class="pf-c-button pf-m-plain" type="button">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-table-ct-container">
|
||||
<form class="ct-form">
|
||||
<label class="control-label" for="system_information_hardware_text" translate="yes">Hardware</label>
|
||||
<span> <!-- wrap the <a> so that it doesn't stretch to the whole page width; otherwise tooltip looks bad -->
|
||||
<a tabindex="0" id="system_information_hardware_text"></a>
|
||||
</span>
|
||||
|
||||
<label class="control-label" for="system_information_asset_tag_text" translate="yes">Asset Tag</label>
|
||||
<span id="system_information_asset_tag_text"></span>
|
||||
|
||||
<label class="control-label" for="system_machine_id" translate="yes">Machine ID</label>
|
||||
<span id="system_machine_id"></span>
|
||||
|
||||
<label class="control-label" for="system_information_os_text" translate="yes">Operating System</label>
|
||||
<span id="system_information_os_text"></span>
|
||||
|
||||
<div role="group" class="system-information-updates">
|
||||
<div>
|
||||
<span id="system_information_updates_icon"></span>
|
||||
<span id="system_information_updates_text"></span>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<span id="insights_icon"></span>
|
||||
<span id="insights_text"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="control-label" for="system-ssh-keys-link" translate="yes">Secure Shell Keys</label>
|
||||
<a tabindex="0" id="system-ssh-keys-link" translate="yes" data-toggle="modal"
|
||||
data-target="#system_information_ssh_keys">Show fingerprints</a>
|
||||
|
||||
<label class="control-label hidden" for="system-ostree-version-link" translate="yes">Version</label>
|
||||
<a tabindex="0" id="system-ostree-version-link" class="hidden"></a>
|
||||
|
||||
<label class="control-label" for="system_information_hostname_button" translate="yes">Host Name</label>
|
||||
<span id="hostname-tooltip">
|
||||
<a tabindex="0" class="hostname-privileged" id="system_information_hostname_button"></a>
|
||||
</span>
|
||||
|
||||
<label class="control-label" for="system-info-domain" translate="yes" hidden>Domain</label>
|
||||
<span id="system-info-domain" hidden></span>
|
||||
|
||||
<label class="control-label" for="system_information_systime_button" translate="yes">System Time</label>
|
||||
<div role="group">
|
||||
<span id="systime-tooltip">
|
||||
<a tabindex="0" class="systime-privileged" id="system_information_systime_button"></a>
|
||||
</span>
|
||||
<a tabindex="0" hidden id="system_information_systime_ntp_status"
|
||||
tabindex="0" role="button" data-toggle="tooltip"
|
||||
data-placement="bottom" data-html="true" >
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<label class="control-label" for="shutdown-action" translate="yes">Power Options</label>
|
||||
<div id="shutdown-group" class="btn-group">
|
||||
<button class="btn btn-default shutdown-privileged" id="shutdown-action" data-action="restart"
|
||||
data-container="body" translate="yes">Restart</button>
|
||||
<button data-toggle="dropdown" class="btn
|
||||
btn-default dropdown-toggle
|
||||
shutdown-privileged"
|
||||
aria-labelledby="system_information_power_options_label">
|
||||
<i class="fa fa-caret-down pf-c-context-selector__toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul role="menu" class="dropdown-menu">
|
||||
<li class="presentation">
|
||||
<a tabindex="0" role="menuitem" data-action="restart" translate="yes">Restart</a>
|
||||
</li>
|
||||
<li class="presentation">
|
||||
<a tabindex="0" role="menuitem" data-action="shutdown" translate="yes">Shut Down</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<label class="control-label" for="tuned-status-button" translate="yes">Performance Profile</label>
|
||||
<span id="system-info-performance" hidden></span>
|
||||
|
||||
<label class="control-label" for="server-pmlogger-switch" translate="yes" hidden>Store Metrics</label>
|
||||
<div id="server-pmlogger-switch" hidden>
|
||||
</div>
|
||||
|
||||
<a tabindex="0" id="system-information-enable-pcp-link" hidden>
|
||||
<span class="pficon pficon-info"></span>
|
||||
<span translate>Enable stored metrics…</span>
|
||||
</a>
|
||||
|
||||
<label class="control-label" for="page_status_notifications" translate="yes">System Health</label>
|
||||
<div id="page_status_notifications">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="system_information_ssh_keys" tabindex="-1"
|
||||
role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" translate="yes">Machine SSH Key Fingerprints</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="spinner spinner-lg"></div>
|
||||
<div class="pf-c-alert pf-m-danger pf-m-inline dialog-error" aria-label="inline danger alert" hidden>
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title"></h4>
|
||||
</div>
|
||||
<div class="content" hidden></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" translate>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<script id="ntp-status-icon-tmpl" type="x-template/mustache">
|
||||
{{#Synched}}
|
||||
<span class="fa fa-lg fa-info-circle"></span>
|
||||
{{/Synched}}
|
||||
{{^Synched}}
|
||||
{{#Server}}
|
||||
<span class="spinner spinner-xs spinner-inline"></span>
|
||||
{{/Server}}
|
||||
{{^Server}}
|
||||
<span class="fa fa-lg fa-exclamation-circle"></span>
|
||||
{{/Server}}
|
||||
{{/Synched}}
|
||||
</script>
|
||||
|
||||
<script id="ntp-status-tmpl" type="x-template/mustache">
|
||||
{{#Synched}}
|
||||
{{#Server}}
|
||||
<div translate="yes">Synchronized with {{Server}}</div>
|
||||
{{/Server}}
|
||||
{{^Server}}
|
||||
<div translate="yes">Synchronized</div>
|
||||
{{/Server}}
|
||||
{{/Synched}}
|
||||
{{^Synched}}
|
||||
{{#Server}}
|
||||
<div translate="yes">Trying to synchronize with {{Server}}</div>
|
||||
{{/Server}}
|
||||
{{^Server}}
|
||||
<div translate="yes">Not synchronized</div>
|
||||
{{#service}}
|
||||
<a tabindex="0" data-goto-service="{{.}}" class="small-messages" translate>Log messages</a>
|
||||
{{/service}}
|
||||
{{/Server}}
|
||||
{{/Synched}}
|
||||
{{#SubStatus}}
|
||||
<div class="small-messages">{{SubStatus}}</div>
|
||||
{{/SubStatus}}
|
||||
</script>
|
||||
|
||||
<script id="ntp-servers-tmpl" type="x-template/mustache">
|
||||
<div class="systime-inline">
|
||||
{{#NTPServers}}
|
||||
<form class="form-inline">
|
||||
<button data-action="add" data-index="{{index}}" class="btn btn-default fa fa-plus"></button>
|
||||
<button data-action="del" data-index="{{index}}" class="btn btn-default pficon-close"></button>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" value="{{Value}}" placeholder="{{Placeholder}}">
|
||||
</div>
|
||||
</form>
|
||||
{{/NTPServers}}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<div class="modal" id="system_information_change_hostname" tabindex="-1"
|
||||
role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" translate>Change Host Name</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="form-table-ct">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label" for="sich-pretty-hostname"
|
||||
translate="yes">Pretty Host Name</label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="sich-pretty-hostname" class="form-control">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label" for="sich-hostname"
|
||||
translate="yes">Real Host Name</label>
|
||||
</td>
|
||||
<td>
|
||||
<div id=sich-hostname-error>
|
||||
<input id="sich-hostname" class="form-control">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="has-error">
|
||||
<span id="sich-note-1" class="help-block"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="has-error">
|
||||
<span id="sich-note-2" class="help-block"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" translate>Cancel</button>
|
||||
<button class="btn btn-primary" id="sich-apply-button" translate>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="ntp-servers-tmpl" type="x-template/mustache">
|
||||
<div class="systime-inline">
|
||||
{{#NTPServers}}
|
||||
<form class="form-inline">
|
||||
<button data-action="add" data-index="{{index}}" class="btn btn-default fa fa-plus"></button>
|
||||
<button data-action="del" data-index="{{index}}" class="btn btn-default pficon-close"></button>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" value="{{Value}}" placeholder="{{Placeholder}}">
|
||||
<div class="modal" id="system_information_change_systime" tabindex="-1"
|
||||
role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" translate>Change System Time</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="form-table-ct">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label" for="systime-timezones" translate="yes">Time Zone</label>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control" id="systime-timezones">
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="has-error">
|
||||
<span id="systime-timezone-error" class="help-block" translate="yes">Invalid time zone</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="control-label" for="change_systime"
|
||||
translate="yes">Set Time</label></td>
|
||||
<td>
|
||||
<div class="btn-group bootstrap-select dropdown form-control" id="change_systime">
|
||||
<button class="btn btn-default dropdown-toggle" type="button"
|
||||
data-toggle="dropdown">
|
||||
<span class="pull-left" translate="yes">Manually</span>
|
||||
<i class="fa fa-caret-down pf-c-context-selector__toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li value="manual_time"><a tabindex="0" translate="yes">Manually</a></li>
|
||||
<li value="ntp_time"><a tabindex="0" translate="yes">Automatically using NTP</a></li>
|
||||
<li value="ntp_time_custom"><a tabindex="0" translate="yes">Automatically using specific NTP servers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="systime-manual-row">
|
||||
<td></td>
|
||||
<td>
|
||||
<input type='text' class="form-control" id="systime-date-input"/>
|
||||
<input type='text' class="form-control" id="systime-time-hours"/>
|
||||
:
|
||||
<input type='text' class="form-control" id="systime-time-minutes"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="systime-manual-error-row">
|
||||
<td>
|
||||
</td>
|
||||
<td class="has-error">
|
||||
<span id="systime-parse-error" class="help-block"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="systime-ntp-servers-row">
|
||||
<td></td>
|
||||
<td id="systime-ntp-servers">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" translate>Cancel</button>
|
||||
<button class="btn btn-primary" id="systime-apply-button" translate>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{/NTPServers}}
|
||||
</div>
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="system_information_change_systime" tabindex="-1"
|
||||
role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" translate>Change System Time</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="form-table-ct">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label" for="systime-timezones" translate="yes">Time Zone</label>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control" id="systime-timezones">
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="has-error">
|
||||
<span id="systime-timezone-error" class="help-block" translate="yes">Invalid time zone</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="control-label" for="change_systime"
|
||||
translate="yes">Set Time</label></td>
|
||||
<td>
|
||||
<div class="btn-group bootstrap-select dropdown form-control" id="change_systime">
|
||||
<button class="btn btn-default dropdown-toggle" type="button"
|
||||
data-toggle="dropdown">
|
||||
<span class="pull-left" translate="yes">Manually</span>
|
||||
<i class="fa fa-caret-down pf-c-context-selector__toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li value="manual_time"><a tabindex="0" translate="yes">Manually</a></li>
|
||||
<li value="ntp_time"><a tabindex="0" translate="yes">Automatically using NTP</a></li>
|
||||
<li value="ntp_time_custom"><a tabindex="0" translate="yes">Automatically using specific NTP servers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="systime-manual-row">
|
||||
<td></td>
|
||||
<td>
|
||||
<input type='text' class="form-control" id="systime-date-input"/>
|
||||
<input type='text' class="form-control" id="systime-time-hours"/>
|
||||
:
|
||||
<input type='text' class="form-control" id="systime-time-minutes"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="systime-manual-error-row">
|
||||
<td>
|
||||
</td>
|
||||
<td class="has-error">
|
||||
<span id="systime-parse-error" class="help-block"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="systime-ntp-servers-row">
|
||||
<td></td>
|
||||
<td id="systime-ntp-servers">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" translate>Cancel</button>
|
||||
<button class="btn btn-primary" id="systime-apply-button" translate>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script id="ssh-host-keys-tmpl" type="x-template/mustache">
|
||||
<div class="list-group dialog-list-ct">
|
||||
{{#keys}}
|
||||
<div class="list-group-item">
|
||||
<p>{{ title }}</p>
|
||||
{{#fps}}
|
||||
<small>{{.}}</small>
|
||||
{{/fps}}
|
||||
</div>
|
||||
{{/keys}}
|
||||
{{^keys}}
|
||||
<div class="list-group-item">
|
||||
<p translate="yes">No host keys found.</p>
|
||||
</div>
|
||||
{{/keys}}
|
||||
</div>
|
||||
</script>
|
||||
<div class="modal" id="system_information_ssh_keys" tabindex="-1"
|
||||
role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" translate="yes">Machine SSH Key Fingerprints</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="spinner spinner-lg"></div>
|
||||
<div class="pf-c-alert pf-m-danger pf-m-inline dialog-error" aria-label="inline danger alert" hidden>
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title"></h4>
|
||||
</div>
|
||||
<div class="content" hidden></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" translate>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal" id="shutdown-dialog" tabindex="-1" role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title"></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<textarea class="form-control">
|
||||
</textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label translate="yes">Delay</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group bootstrap-select dropdown form-control">
|
||||
<button class="btn btn-default dropdown-toggle" type="button"
|
||||
data-toggle="dropdown">
|
||||
<span class="pull-left" translate="yes">1 Minute</span>
|
||||
<i class="fa fa-caret-down pf-c-context-selector__toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li value="1"><a tabindex="0" translate="yes">1 Minute</a></li>
|
||||
<li value="5"><a tabindex="0" translate="yes">5 Minutes</a></li>
|
||||
<li value="20"><a tabindex="0" translate="yes">20 Minutes</a></li>
|
||||
<li value="40"><a tabindex="0" translate="yes">40 Minutes</a></li>
|
||||
<li value="60"><a tabindex="0" translate="yes">60 Minutes</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li value="0"><a tabindex="0" translate="yes">No Delay</a></li>
|
||||
<li value="x"><a tabindex="0" translate="yes">Specific Time</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<input class="form-control shutdown-date" type="text">
|
||||
<input class="form-control shutdown-hours" type="text">
|
||||
:
|
||||
<input class="form-control shutdown-minutes" type="text">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" translate="yes" data-dismiss="modal">Cancel</button>
|
||||
<button class="btn btn-danger"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal" id="system_information_change_hostname" tabindex="-1"
|
||||
role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" translate>Change Host Name</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="form-table-ct">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label" for="sich-pretty-hostname"
|
||||
translate="yes">Pretty Host Name</label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="sich-pretty-hostname" class="form-control">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label" for="sich-hostname"
|
||||
translate="yes">Real Host Name</label>
|
||||
</td>
|
||||
<td>
|
||||
<div id=sich-hostname-error>
|
||||
<input id="sich-hostname" class="form-control">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="has-error">
|
||||
<span id="sich-note-1" class="help-block"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="has-error">
|
||||
<span id="sich-note-2" class="help-block"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" translate>Cancel</button>
|
||||
<button class="btn btn-primary" id="sich-apply-button" translate>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="shutdown-dialog" tabindex="-1" role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title"></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<textarea class="form-control">
|
||||
</textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label translate="yes">Delay</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group bootstrap-select dropdown form-control">
|
||||
<button class="btn btn-default dropdown-toggle" type="button"
|
||||
data-toggle="dropdown">
|
||||
<span class="pull-left" translate="yes">1 Minute</span>
|
||||
<i class="fa fa-caret-down pf-c-context-selector__toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li value="1"><a tabindex="0" translate="yes">1 Minute</a></li>
|
||||
<li value="5"><a tabindex="0" translate="yes">5 Minutes</a></li>
|
||||
<li value="20"><a tabindex="0" translate="yes">20 Minutes</a></li>
|
||||
<li value="40"><a tabindex="0" translate="yes">40 Minutes</a></li>
|
||||
<li value="60"><a tabindex="0" translate="yes">60 Minutes</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li value="0"><a tabindex="0" translate="yes">No Delay</a></li>
|
||||
<li value="x"><a tabindex="0" translate="yes">Specific Time</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<input class="form-control shutdown-date" type="text">
|
||||
<input class="form-control shutdown-hours" type="text">
|
||||
:
|
||||
<input class="form-control shutdown-minutes" type="text">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" translate="yes" data-dismiss="modal">Cancel</button>
|
||||
<button class="btn btn-danger"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="confirmation-dialog" tabindex="-1" role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="confirmation-dialog-title"></h4>
|
||||
</div>
|
||||
<div class="modal-body" id="confirmation-dialog-body">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" translate="yes" id="confirmation-dialog-cancel">Cancel</button>
|
||||
<button class="btn btn-danger" id="confirmation-dialog-confirm">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="system.js"></script>
|
||||
<script src="../domain/domain.js"></script>
|
||||
<script src="../performance/performance.js"></script>
|
||||
<script src="../domain/domain.js"></script>
|
||||
<script src="../performance/performance.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -27,8 +27,6 @@ import ReactDOM from 'react-dom';
|
|||
import React from 'react';
|
||||
import { EmptyStatePanel } from "cockpit-components-empty-state.jsx";
|
||||
|
||||
import "./logs.css";
|
||||
|
||||
$(function() {
|
||||
cockpit.translate();
|
||||
const _ = cockpit.gettext;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@import "../lib/table.css";
|
||||
@import "../lib/journal.css";
|
||||
@import "./system-global.css";
|
||||
@import "./system-global.less";
|
||||
|
||||
/* Make sure to not break log message lines in order to preserve information */
|
||||
#journal-entry .info-table-ct td {
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
"menu": {
|
||||
"index": {
|
||||
"label": "System",
|
||||
"label": "Overview",
|
||||
"order": 10,
|
||||
"keywords": [
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -16,273 +16,116 @@
|
|||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { mustache } from "mustache";
|
||||
import $ from "jquery";
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardBody, Button } from '@patternfly/react-core';
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { OnOffSwitch } from "cockpit-components-onoff.jsx";
|
||||
|
||||
import "polyfills.js";
|
||||
import * as service from "service.js";
|
||||
import host_keys_script from "raw-loader!./ssh-list-host-keys.sh";
|
||||
import cockpit from "cockpit";
|
||||
import * as machine_info from "machine-info.js";
|
||||
import $ from "jquery";
|
||||
import { mustache } from "mustache";
|
||||
import * as packagekit from "packagekit.js";
|
||||
import { install_dialog } from "cockpit-components-install-dialog.jsx";
|
||||
import * as service from "service.js";
|
||||
import { shutdown } from "./shutdown.js";
|
||||
import host_keys_script from "raw-loader!./ssh-list-host-keys.sh";
|
||||
import { page_status } from "notifications";
|
||||
import { set_page_link, dialog_setup, page_show } from './helpers.js';
|
||||
|
||||
import { PageStatusNotifications } from "./page-status.jsx";
|
||||
|
||||
import "form-layout.less";
|
||||
import { ServerTime } from './serverTime.js';
|
||||
|
||||
/* These add themselves to jQuery so just including is enough */
|
||||
import "patterns";
|
||||
import "bootstrap-datepicker/dist/js/bootstrap-datepicker";
|
||||
import "patternfly-bootstrap-combobox/js/bootstrap-combobox";
|
||||
|
||||
import "./configurationCard.less";
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
|
||||
var permission = cockpit.permission({ admin: true });
|
||||
$(permission).on("changed", update_hostname_privileged);
|
||||
$(permission).on("changed", update_shutdown_privileged);
|
||||
$(permission).on("changed", update_systime_privileged);
|
||||
|
||||
function update_hostname_privileged() {
|
||||
$(".hostname-privileged").update_privileged(
|
||||
permission, cockpit.format(
|
||||
_("The user <b>$0</b> is not permitted to modify hostnames"),
|
||||
permission.user ? permission.user.name : ''), null, $('#hostname-tooltip')
|
||||
);
|
||||
function dialog_setup(d) {
|
||||
d.setup();
|
||||
$('#' + d.id)
|
||||
.on('show.bs.modal', function(event) {
|
||||
if (event.target.id === d.id)
|
||||
d.enter();
|
||||
})
|
||||
.on('shown.bs.modal', function(event) {
|
||||
if (event.target.id === d.id)
|
||||
d.show();
|
||||
})
|
||||
.on('hidden.bs.modal', function(event) {
|
||||
if (event.target.id === d.id)
|
||||
d.leave();
|
||||
});
|
||||
}
|
||||
|
||||
function update_shutdown_privileged() {
|
||||
$(".shutdown-privileged").update_privileged(
|
||||
permission, cockpit.format(
|
||||
_("The user <b>$0</b> is not permitted to shutdown or restart this server"),
|
||||
permission.user ? permission.user.name : '')
|
||||
);
|
||||
}
|
||||
export class ConfigurationCard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
pmlogger_switch_visible: false,
|
||||
pcp_link_visible: false,
|
||||
serverTime: '',
|
||||
};
|
||||
|
||||
function update_systime_privileged() {
|
||||
$(".systime-privileged").update_privileged(
|
||||
permission, cockpit.format(
|
||||
_("The user <b>$0</b> is not permitted to change the system time"),
|
||||
permission.user ? permission.user.name : ''), null, $('#systime-tooltip')
|
||||
);
|
||||
}
|
||||
this.pmcd_service = service.proxy("pmcd");
|
||||
this.pmlogger_service = service.proxy("pmlogger");
|
||||
this.pmlogger_exists = false;
|
||||
this.packagekit_exists = false;
|
||||
|
||||
function debug() {
|
||||
if (window.debugging == "all" || window.debugging == "system")
|
||||
console.debug.apply(console, arguments);
|
||||
}
|
||||
this.onPmLoggerSwitchChange = this.onPmLoggerSwitchChange.bind(this);
|
||||
this.update_pmlogger_row = this.update_pmlogger_row.bind(this);
|
||||
this.pmlogger_service_changed = this.pmlogger_service_changed.bind(this);
|
||||
|
||||
function ServerTime() {
|
||||
var self = this;
|
||||
this.host_keys_show = this.host_keys_show.bind(this);
|
||||
this.host_keys_hide = this.host_keys_hide.bind(this);
|
||||
}
|
||||
|
||||
var client = cockpit.dbus('org.freedesktop.timedate1');
|
||||
var timedate = client.proxy();
|
||||
componentDidMount() {
|
||||
dialog_setup(new PageSystemInformationChangeHostname());
|
||||
permission.addEventListener("changed", this.update_hostname_privileged);
|
||||
this.update_hostname_privileged();
|
||||
|
||||
var time_offset = null;
|
||||
var remote_offset = null;
|
||||
dialog_setup(this.change_systime_dialog = new PageSystemInformationChangeSystime());
|
||||
this.systime_setup();
|
||||
permission.addEventListener("changed", this.update_systime_privileged);
|
||||
this.update_systime_privileged();
|
||||
|
||||
this.client = client;
|
||||
|
||||
self.timedate = timedate;
|
||||
|
||||
this.ntp_waiting_value = null;
|
||||
this.ntp_waiting_resolve = null;
|
||||
|
||||
self.timedate1_service = service.proxy("dbus-org.freedesktop.timedate1.service");
|
||||
self.timesyncd_service = service.proxy("systemd-timesyncd.service");
|
||||
|
||||
/*
|
||||
* The time we return from here as its UTC time set to the
|
||||
* server time. This is the only way to get predictable
|
||||
* behavior and formatting of a Date() object in the absence of
|
||||
* IntlDateFormat and friends.
|
||||
*/
|
||||
Object.defineProperty(self, 'utc_fake_now', {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
var offset = time_offset + remote_offset;
|
||||
return new Date(offset + (new Date()).valueOf());
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(self, 'now', {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return new Date(time_offset + (new Date()).valueOf());
|
||||
}
|
||||
});
|
||||
|
||||
self.format = function format(and_time) {
|
||||
var string = self.utc_fake_now.toISOString();
|
||||
if (!and_time)
|
||||
return string.split('T')[0];
|
||||
var pos = string.lastIndexOf(':');
|
||||
if (pos !== -1)
|
||||
string = string.substring(0, pos);
|
||||
return string.replace('T', ' ');
|
||||
};
|
||||
|
||||
self.updateInterval = window.setInterval(function() {
|
||||
$(self).triggerHandler("changed");
|
||||
}, 30000);
|
||||
|
||||
self.wait = function wait() {
|
||||
if (remote_offset === null)
|
||||
return self.update();
|
||||
return cockpit.resolve();
|
||||
};
|
||||
|
||||
self.update = function update() {
|
||||
return cockpit.spawn(["date", "+%s:%z"], { err: "message" })
|
||||
.done(function(data) {
|
||||
const parts = data.trim().split(":");
|
||||
const timems = parseInt(parts[0], 10) * 1000;
|
||||
let tzmin = parseInt(parts[1].slice(-2), 10);
|
||||
const tzhour = parseInt(parts[1].slice(0, -2));
|
||||
if (tzhour < 0)
|
||||
tzmin = -tzmin;
|
||||
const offsetms = (tzhour * 3600000) + tzmin * 60000;
|
||||
const now = new Date();
|
||||
time_offset = (timems - now.valueOf());
|
||||
remote_offset = offsetms;
|
||||
$(self).triggerHandler("changed");
|
||||
})
|
||||
.fail(function(ex) {
|
||||
console.log("Couldn't calculate server time offset: " + cockpit.message(ex));
|
||||
});
|
||||
};
|
||||
|
||||
self.change_time = function change_time(datestr, hourstr, minstr) {
|
||||
var dfd = $.Deferred();
|
||||
|
||||
/*
|
||||
* The browser is brain dead when it comes to dates. But even if
|
||||
* it wasn't, or we loaded a library like moment.js, there is no
|
||||
* way to make sense of this date without a round trip to the
|
||||
* server ... the timezone is really server specific.
|
||||
*/
|
||||
cockpit.spawn(["date", "--date=" + datestr + " " + hourstr + ":" + minstr, "+%s"])
|
||||
.fail(function(ex) {
|
||||
dfd.reject(ex);
|
||||
})
|
||||
.done(function(data) {
|
||||
var seconds = parseInt(data.trim(), 10);
|
||||
timedate.call('SetTime', [seconds * 1000 * 1000, false, true])
|
||||
.fail(function(ex) {
|
||||
dfd.reject(ex);
|
||||
})
|
||||
.done(function() {
|
||||
self.update();
|
||||
dfd.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return dfd;
|
||||
};
|
||||
|
||||
self.poll_ntp_synchronized = function poll_ntp_synchronized() {
|
||||
client.call(timedate.path,
|
||||
"org.freedesktop.DBus.Properties", "Get", ["org.freedesktop.timedate1", "NTPSynchronized"])
|
||||
.fail(function(error) {
|
||||
if (error.name != "org.freedesktop.DBus.Error.UnknownProperty" &&
|
||||
error.problem != "not-found")
|
||||
console.log("can't get NTPSynchronized property", error);
|
||||
})
|
||||
.done(function(result) {
|
||||
var ifaces = { "org.freedesktop.timedate1": { NTPSynchronized: result[0].v } };
|
||||
var data = { };
|
||||
data[timedate.path] = ifaces;
|
||||
client.notify(data);
|
||||
});
|
||||
};
|
||||
|
||||
self.ntp_updated = function ntp_updated(path, iface, member, args) {
|
||||
if (!self.ntp_waiting_resolve || !args[1].NTP)
|
||||
return;
|
||||
if (self.ntp_waiting_value !== args[1].NTP.v)
|
||||
console.warn("Unexpected value of NTP");
|
||||
self.ntp_waiting_resolve();
|
||||
self.ntp_waiting_resolve = null;
|
||||
};
|
||||
|
||||
self.close = function close() {
|
||||
client.close();
|
||||
};
|
||||
|
||||
self.update();
|
||||
}
|
||||
|
||||
var change_systime_dialog;
|
||||
|
||||
PageServer.prototype = {
|
||||
_init: function() {
|
||||
this.id = "server";
|
||||
this.server_time = null;
|
||||
this.client = null;
|
||||
this.hostname_proxy = null;
|
||||
this.unregistered = false;
|
||||
},
|
||||
|
||||
getTitle: function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
setup: function() {
|
||||
var self = this;
|
||||
update_hostname_privileged();
|
||||
|
||||
cockpit.file("/etc/motd").watch(function(content) {
|
||||
if (content)
|
||||
content = content.trimRight();
|
||||
if (content && content != cockpit.localStorage.getItem('dismissed-motd')) {
|
||||
$('#motd').text(content);
|
||||
$('#motd-box').show();
|
||||
} else {
|
||||
$('#motd-box').hide();
|
||||
}
|
||||
// To help the tests known when we have loaded motd
|
||||
$('#motd-box').attr('data-stable', 'yes');
|
||||
$(this.pmlogger_service).on("changed", data => this.pmlogger_service_changed());
|
||||
this.pmlogger_service_changed();
|
||||
packagekit.detect().then(exists => {
|
||||
this.packagekit_exists = exists;
|
||||
this.update_pmlogger_row();
|
||||
});
|
||||
|
||||
$('#motd-box button.pf-c-button').click(function() {
|
||||
cockpit.localStorage.setItem('dismissed-motd', $('#motd').text());
|
||||
$('#motd-box').hide();
|
||||
});
|
||||
$("#system_information_ssh_keys").on("hide.bs.modal", () => this.host_keys_hide());
|
||||
}
|
||||
|
||||
$('#shutdown-group [data-action]').on("click", function(ev) {
|
||||
// don't let the click "fall through" to the dialog that we are about to open
|
||||
ev.preventDefault();
|
||||
self.shutdown($(this).attr('data-action'));
|
||||
});
|
||||
update_hostname_privileged() {
|
||||
$(".hostname-privileged").update_privileged(
|
||||
permission, cockpit.format(
|
||||
_("The user <b>$0</b> is not permitted to modify hostnames"),
|
||||
permission.user ? permission.user.name : ''),
|
||||
null, $("#system_information_hostname_tooltip")
|
||||
);
|
||||
// this really needs the disabled attribute, not the disabled class
|
||||
if (permission.allowed === false)
|
||||
$(".hostname-privileged").attr("disabled", "disabled");
|
||||
else
|
||||
$(".hostname-privileged").removeAttr("disabled");
|
||||
}
|
||||
|
||||
$('#system-ostree-version-link').on('click', function() {
|
||||
cockpit.jump("/updates", cockpit.transport.host);
|
||||
});
|
||||
update_systime_privileged() {
|
||||
$(".systime-privileged").update_privileged(
|
||||
permission, cockpit.format(
|
||||
_("The user <b>$0</b> is not permitted to change the system time"),
|
||||
permission.user ? permission.user.name : ''), null, $('#systime-tooltip')
|
||||
);
|
||||
}
|
||||
|
||||
$('#system_information_hostname_button').on('click', function(ev) {
|
||||
// you can't disable standard links, so implement this manually; realmd might disable host name changing
|
||||
if (ev.target.getAttribute("disabled")) {
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
PageSystemInformationChangeHostname.client = self.client;
|
||||
$('#system_information_change_hostname').modal('show');
|
||||
});
|
||||
|
||||
$('#system_information_systime_button').on('click', function() {
|
||||
change_systime_dialog.display(self.server_time);
|
||||
});
|
||||
systime_setup() {
|
||||
const self = this;
|
||||
|
||||
self.server_time = new ServerTime();
|
||||
$(self.server_time).on("changed", function() {
|
||||
$('#system_information_systime_button').text(self.server_time.format(true));
|
||||
self.setState({ serverTime: self.server_time.format(true) });
|
||||
});
|
||||
|
||||
self.server_time.client.subscribe({
|
||||
|
@ -296,17 +139,6 @@ PageServer.prototype = {
|
|||
self.ntp_status_icon_tmpl = $("#ntp-status-icon-tmpl").html();
|
||||
mustache.parse(this.ntp_status_icon_tmpl);
|
||||
|
||||
self.ssh_host_keys_tmpl = $("#ssh-host-keys-tmpl").html();
|
||||
mustache.parse(this.ssh_host_keys_tmpl);
|
||||
|
||||
$("#system_information_ssh_keys").on("show.bs.modal", function() {
|
||||
self.host_keys_show();
|
||||
});
|
||||
|
||||
$("#system_information_ssh_keys").on("hide.bs.modal", function() {
|
||||
self.host_keys_hide();
|
||||
});
|
||||
|
||||
var $ntp_status = $('#system_information_systime_ntp_status');
|
||||
|
||||
function update_ntp_status() {
|
||||
|
@ -349,7 +181,7 @@ PageServer.prototype = {
|
|||
$ntp_status.attr("data-original-title", tooltip_html);
|
||||
|
||||
var icon_html = mustache.render(self.ntp_status_icon_tmpl, model);
|
||||
$ntp_status.html(icon_html);
|
||||
self.setState({ ntp_status_icon: { __html: icon_html } });
|
||||
}
|
||||
|
||||
$ntp_status.tooltip();
|
||||
|
@ -364,258 +196,11 @@ PageServer.prototype = {
|
|||
window.setInterval(function() {
|
||||
self.server_time.poll_ntp_synchronized();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
$('#server').on('click', "[data-goto-service]", function() {
|
||||
var service = $(this).attr("data-goto-service");
|
||||
cockpit.jump("/system/services/#/" + window.encodeURIComponent(service),
|
||||
cockpit.transport.host);
|
||||
});
|
||||
|
||||
var pmcd_service = service.proxy("pmcd");
|
||||
var pmlogger_service = service.proxy("pmlogger");
|
||||
var pmlogger_promise;
|
||||
var pmlogger_exists = false;
|
||||
var packagekit_exists = false;
|
||||
|
||||
function update_pmlogger_row(force_disable) {
|
||||
var logger_switch = $("#server-pmlogger-switch");
|
||||
var enable_pcp = $('#system-information-enable-pcp-link');
|
||||
if (!pmlogger_exists) {
|
||||
enable_pcp.toggle(packagekit_exists);
|
||||
logger_switch.hide();
|
||||
logger_switch.prev().hide();
|
||||
} else if (!pmlogger_promise) {
|
||||
enable_pcp.hide();
|
||||
logger_switch.show();
|
||||
logger_switch.prev().show();
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(OnOffSwitch, {
|
||||
state: pmlogger_service.state === "running",
|
||||
disabled: pmlogger_service.state == "starting" || force_disable,
|
||||
onChange: onPmLoggerSwitchChange
|
||||
}),
|
||||
document.getElementById('server-pmlogger-switch')
|
||||
);
|
||||
}
|
||||
|
||||
function pmlogger_service_changed() {
|
||||
pmlogger_exists = pmlogger_service.exists;
|
||||
|
||||
/* HACK: The pcp packages on Ubuntu and Debian include SysV init
|
||||
* scripts in /etc, which stay around when removing (as opposed to
|
||||
* purging) the package. Systemd treats those as valid units, even
|
||||
* if they're not backed by packages anymore. Thus,
|
||||
* pmlogger_service.exists will be true. Check for the binary
|
||||
* directly to make sure the package is actually available.
|
||||
*/
|
||||
if (pmlogger_exists) {
|
||||
cockpit.spawn(["which", "pmlogger"], { err: "ignore" })
|
||||
.fail(function() {
|
||||
pmlogger_exists = false;
|
||||
})
|
||||
.always(() => update_pmlogger_row());
|
||||
} else {
|
||||
update_pmlogger_row();
|
||||
}
|
||||
}
|
||||
|
||||
packagekit.detect().then(function(exists) {
|
||||
packagekit_exists = exists;
|
||||
update_pmlogger_row();
|
||||
});
|
||||
|
||||
function onPmLoggerSwitchChange(enable) {
|
||||
if (!pmlogger_exists)
|
||||
return;
|
||||
|
||||
update_pmlogger_row(true);
|
||||
|
||||
if (enable) {
|
||||
pmlogger_promise = Promise.all([
|
||||
pmcd_service.enable(),
|
||||
pmcd_service.start(),
|
||||
pmlogger_service.enable(),
|
||||
pmlogger_service.start()
|
||||
])
|
||||
.catch(function(error) {
|
||||
console.warn("Enabling pmlogger failed", error);
|
||||
});
|
||||
} else {
|
||||
pmlogger_promise = Promise.all([pmlogger_service.disable(), pmlogger_service.stop()])
|
||||
.catch(function(error) {
|
||||
console.warn("Disabling pmlogger failed", error);
|
||||
});
|
||||
}
|
||||
pmlogger_promise.finally(function() {
|
||||
pmlogger_promise = null;
|
||||
pmlogger_service_changed();
|
||||
});
|
||||
}
|
||||
|
||||
$(pmlogger_service).on('changed', pmlogger_service_changed);
|
||||
pmlogger_service_changed();
|
||||
|
||||
function refresh_os_updates_state() {
|
||||
const status = page_status.get("updates") || { };
|
||||
const details = status.details || { };
|
||||
$("#system_information_updates_icon").attr("class", details.icon || "");
|
||||
$("#system_information_updates_icon").toggle(!!details.icon);
|
||||
set_page_link("#system_information_updates_text", details.link || "updates",
|
||||
details.text || status.title || "");
|
||||
}
|
||||
|
||||
refresh_os_updates_state();
|
||||
$(page_status).on("changed", refresh_os_updates_state);
|
||||
|
||||
var insights_client_timer = service.proxy("insights-client.timer");
|
||||
|
||||
function refresh_insights_status() {
|
||||
const subfeats = (cockpit.manifests.subscriptions && cockpit.manifests.subscriptions.features) || { };
|
||||
if (subfeats.insights && insights_client_timer.exists && !insights_client_timer.enabled) {
|
||||
$("#insights_icon").attr("class", "pficon pficon-warning-triangle-o");
|
||||
set_page_link("#insights_text", "subscriptions", _("Not connected to Insights"));
|
||||
$("#insights_icon, #insights_text").show();
|
||||
} else {
|
||||
$("#insights_icon, #insights_text").hide();
|
||||
}
|
||||
}
|
||||
|
||||
$(insights_client_timer).on("changed", refresh_insights_status);
|
||||
refresh_insights_status();
|
||||
|
||||
// Only link from graphs to available pages
|
||||
set_page_link("#link-disk", "storage", _("Disk I/O"));
|
||||
set_page_link("#link-network", "network", _("Network Traffic"));
|
||||
|
||||
function toggle_health_label(visible) {
|
||||
$('label[for="page_status_notifications"]').toggle(visible);
|
||||
}
|
||||
|
||||
ReactDOM.render(React.createElement(PageStatusNotifications, { toggle_label: toggle_health_label }),
|
||||
document.getElementById('page_status_notifications'));
|
||||
},
|
||||
|
||||
enter: function() {
|
||||
host_keys_show() {
|
||||
var self = this;
|
||||
|
||||
var machine_id = cockpit.file("/etc/machine-id");
|
||||
machine_id.read()
|
||||
.done(function(content) {
|
||||
$("#system_machine_id").text(content);
|
||||
})
|
||||
.fail(function(ex) {
|
||||
console.error("Error reading machine id", ex);
|
||||
})
|
||||
.always(function() {
|
||||
machine_id.close();
|
||||
});
|
||||
|
||||
self.ostree_client = cockpit.dbus('org.projectatomic.rpmostree1',
|
||||
{ superuser : true });
|
||||
$(self.ostree_client).on("close", function() {
|
||||
self.ostree_client = null;
|
||||
});
|
||||
|
||||
self.sysroot = self.ostree_client.proxy('org.projectatomic.rpmostree1.Sysroot',
|
||||
'/org/projectatomic/rpmostree1/Sysroot');
|
||||
$(self.sysroot).on("changed", $.proxy(this, "sysroot_changed"));
|
||||
|
||||
self.client = cockpit.dbus('org.freedesktop.hostname1',
|
||||
{ superuser : "try" });
|
||||
self.hostname_proxy = self.client.proxy('org.freedesktop.hostname1',
|
||||
'/org/freedesktop/hostname1');
|
||||
self.kernel_hostname = null;
|
||||
|
||||
const asset_tag_text = $("#system_information_asset_tag_text");
|
||||
const hardware_text = $("#system_information_hardware_text");
|
||||
hardware_text.tooltip({ title: _("Click to see system hardware information"), placement: "bottom" });
|
||||
machine_info.dmi_info()
|
||||
.done(function(fields) {
|
||||
let vendor = fields.sys_vendor;
|
||||
let name = fields.product_name;
|
||||
if (!vendor || !name) {
|
||||
vendor = fields.board_vendor;
|
||||
name = fields.board_name;
|
||||
}
|
||||
if (!vendor || !name)
|
||||
hardware_text.text(_("Details"));
|
||||
else
|
||||
hardware_text.text(vendor + " " + name);
|
||||
var present = !!(fields.product_serial || fields.chassis_serial);
|
||||
asset_tag_text.text(fields.product_serial || fields.chassis_serial);
|
||||
asset_tag_text.toggle(present);
|
||||
asset_tag_text.prev().toggle(present);
|
||||
})
|
||||
.fail(function(ex) {
|
||||
debug("couldn't read dmi info: " + ex);
|
||||
hardware_text.text(_("Details"));
|
||||
asset_tag_text.toggle(false);
|
||||
asset_tag_text.prev().toggle(false);
|
||||
});
|
||||
|
||||
function hostname_text() {
|
||||
if (!self.hostname_proxy)
|
||||
return;
|
||||
|
||||
var pretty_hostname = self.hostname_proxy.PrettyHostname;
|
||||
var static_hostname = self.hostname_proxy.StaticHostname;
|
||||
|
||||
var str = self.kernel_hostname;
|
||||
if (pretty_hostname && static_hostname && static_hostname != pretty_hostname)
|
||||
str = pretty_hostname + " (" + static_hostname + ")";
|
||||
else if (static_hostname)
|
||||
str = static_hostname;
|
||||
|
||||
if (!str)
|
||||
str = _("Set Host name");
|
||||
var hostname_button = $("#system_information_hostname_button");
|
||||
hostname_button.text(str);
|
||||
if (!hostname_button.attr("disabled")) {
|
||||
hostname_button
|
||||
.attr("title", str)
|
||||
.tooltip('fixTitle');
|
||||
}
|
||||
$("#system_information_os_text").text(self.hostname_proxy.OperatingSystemPrettyName || "");
|
||||
}
|
||||
|
||||
cockpit.spawn(["hostname"], { err: "ignore" })
|
||||
.done(function(output) {
|
||||
self.kernel_hostname = $.trim(output);
|
||||
hostname_text();
|
||||
})
|
||||
.fail(function(ex) {
|
||||
hostname_text();
|
||||
debug("couldn't read kernel hostname: " + ex);
|
||||
});
|
||||
$(self.hostname_proxy).on("changed", hostname_text);
|
||||
},
|
||||
|
||||
show: function() {
|
||||
},
|
||||
|
||||
leave: function() {
|
||||
var self = this;
|
||||
|
||||
$(self.hostname_proxy).off();
|
||||
self.hostname_proxy = null;
|
||||
|
||||
self.client.close();
|
||||
self.client = null;
|
||||
|
||||
$(cockpit).off('.server');
|
||||
|
||||
$(self.sysroot).off();
|
||||
self.sysroot = null;
|
||||
if (self.ostree_client) {
|
||||
self.ostree_client.close();
|
||||
self.ostree_client = null;
|
||||
}
|
||||
},
|
||||
|
||||
host_keys_show: function() {
|
||||
var self = this;
|
||||
$("#system_information_ssh_keys .spinner").toggle(true);
|
||||
$("#system_information_ssh_keys .content").toggle(false);
|
||||
$("#system_information_ssh_keys .pf-c-alert").toggle(false);
|
||||
|
@ -629,9 +214,14 @@ PageServer.prototype = {
|
|||
self.host_keys_update();
|
||||
}, 10 * 1000);
|
||||
self.host_keys_update();
|
||||
},
|
||||
}
|
||||
|
||||
host_keys_update: function() {
|
||||
host_keys_hide() {
|
||||
window.clearInterval(this.host_keys_interval);
|
||||
this.host_keys_interval = null;
|
||||
}
|
||||
|
||||
host_keys_update() {
|
||||
var self = this;
|
||||
var parenthesis = /^\((.*)\)$/;
|
||||
var spinner = $("#system_information_ssh_keys .spinner");
|
||||
|
@ -677,6 +267,9 @@ PageServer.prototype = {
|
|||
return { title: k, fps: keys[k] };
|
||||
});
|
||||
|
||||
self.ssh_host_keys_tmpl = $("#ssh-host-keys-tmpl").html();
|
||||
mustache.parse(self.ssh_host_keys_tmpl);
|
||||
|
||||
tmp = mustache.render(self.ssh_host_keys_tmpl, { keys: arr });
|
||||
content.html(tmp);
|
||||
spinner.toggle(false);
|
||||
|
@ -690,51 +283,155 @@ PageServer.prototype = {
|
|||
$("#system_information_ssh_keys .pf-c-alert h4").text(msg);
|
||||
error.toggle(true);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
host_keys_hide: function() {
|
||||
var self = this;
|
||||
window.clearInterval(self.host_keys_interval);
|
||||
self.host_keys_interval = null;
|
||||
},
|
||||
onPmLoggerSwitchChange(enable) {
|
||||
if (!this.pmlogger_exists)
|
||||
return;
|
||||
|
||||
sysroot_changed: function() {
|
||||
var self = this;
|
||||
var link = $("#system-ostree-version-link");
|
||||
this.update_pmlogger_row(true);
|
||||
|
||||
if (self.sysroot.Booted && self.ostree_client) {
|
||||
var version = "";
|
||||
self.ostree_client.call(self.sysroot.Booted,
|
||||
"org.freedesktop.DBus.Properties", "Get",
|
||||
['org.projectatomic.rpmostree1.OS', "BootedDeployment"])
|
||||
.done(function(result) {
|
||||
if (result && result[0]) {
|
||||
var deployment = result[0].v;
|
||||
if (deployment && deployment.version)
|
||||
version = deployment.version.v;
|
||||
}
|
||||
})
|
||||
.fail(function(ex) {
|
||||
console.log(ex);
|
||||
})
|
||||
.always(function() {
|
||||
link.toggleClass("hidden", !version);
|
||||
link.prev().toggleClass("hidden", !version);
|
||||
link.text(version);
|
||||
});
|
||||
if (enable) {
|
||||
this.pmlogger_promise = Promise.all([
|
||||
this.pmcd_service.enable(),
|
||||
this.pmcd_service.start(),
|
||||
this.pmlogger_service.enable(),
|
||||
this.pmlogger_service.start()
|
||||
]).catch(function(error) {
|
||||
console.warn("Enabling pmlogger failed", error);
|
||||
});
|
||||
} else {
|
||||
link.toggleClass("hidden", true);
|
||||
link.text("");
|
||||
this.pmlogger_promise = Promise.all([this.pmlogger_service.disable(), this.pmlogger_service.stop()])
|
||||
.catch(function(error) {
|
||||
console.warn("Disabling pmlogger failed", error);
|
||||
});
|
||||
}
|
||||
},
|
||||
this.pmlogger_promise.finally(() => {
|
||||
this.pmlogger_promise = null;
|
||||
this.pmlogger_service_changed();
|
||||
});
|
||||
}
|
||||
|
||||
shutdown: function(action_type) {
|
||||
shutdown(action_type, this.server_time);
|
||||
},
|
||||
};
|
||||
update_pmlogger_row(force_disable) {
|
||||
if (!this.pmlogger_exists) {
|
||||
this.setState({ pcp_link_visible: this.packagekit_exists });
|
||||
this.setState({ pmlogger_switch_visible: false });
|
||||
} else if (!this.pmlogger_promise) {
|
||||
this.setState({ pcp_link_visible: false });
|
||||
this.setState({ pmlogger_switch_visible: true });
|
||||
}
|
||||
this.setState({ pm_logger_switch_disabled: force_disable });
|
||||
}
|
||||
|
||||
function PageServer() {
|
||||
this._init();
|
||||
pmlogger_service_changed() {
|
||||
this.pmlogger_exists = this.pmlogger_service.exists;
|
||||
|
||||
/* HACK: The pcp packages on Ubuntu and Debian include SysV init
|
||||
* scripts in /etc, which stay around when removing (as opposed to
|
||||
* purging) the package. Systemd treats those as valid units, even
|
||||
* if they're not backed by packages anymore. Thus,
|
||||
* pmlogger_service.exists will be true. Check for the binary
|
||||
* directly to make sure the package is actually available.
|
||||
*/
|
||||
if (this.pmlogger_exists) {
|
||||
cockpit.spawn(["which", "pmlogger"], { err: "ignore" })
|
||||
.fail(function() {
|
||||
this.pmlogger_exists = false;
|
||||
})
|
||||
.always(() => this.update_pmlogger_row());
|
||||
} else {
|
||||
this.update_pmlogger_row();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Card className="system-configuration">
|
||||
<CardHeader>{_("Configuration")}</CardHeader>
|
||||
<CardBody>
|
||||
<table className="pf-c-table pf-m-grid-md pf-m-compact">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{_("Hostname")}</th>
|
||||
<td>
|
||||
{this.props.hostname && <span id="system_information_hostname_text">{this.props.hostname}</span>}
|
||||
<span id="system_information_hostname_tooltip">
|
||||
<Button variant='link'
|
||||
id="system_information_hostname_button"
|
||||
className="hostname-privileged"
|
||||
isInline
|
||||
onClick={() => $('#system_information_change_hostname').modal('show')}
|
||||
isDisabled={$('system_information_change_hostname').attr("disabled")}
|
||||
aria-label="edit hostname">
|
||||
{this.props.hostname !== "" ? _("edit") : _("Set Hostname")}
|
||||
</Button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">{_("System time")}</th>
|
||||
<td>
|
||||
<span id="systime-tooltip">
|
||||
<Button variant="link" isInline className="systime-privileged"
|
||||
id="system_information_systime_button"
|
||||
onClick={() => this.change_systime_dialog.display(this.server_time)}>
|
||||
{this.state.serverTime}
|
||||
</Button>
|
||||
</span>
|
||||
<a tabIndex="0" hidden id="system_information_systime_ntp_status"
|
||||
role="button" data-toggle="tooltip"
|
||||
data-placement="bottom" data-html="true" dangerouslySetInnerHTML={this.state.ntp_status_icon} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">{_("Domain")}</th>
|
||||
<td><p id="system-info-domain" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">{_("Performance profile")}</th>
|
||||
<td><span id="system-info-performance" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">{_("Secure Shell keys")}</th>
|
||||
<td>
|
||||
<Button variant="link" isInline id="system-ssh-keys-link" data-toggle="modal" onClick={this.host_keys_show}
|
||||
data-target="#system_information_ssh_keys">{_("Show fingerprints")}</Button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{this.state.pmlogger_switch_visible &&
|
||||
<tr>
|
||||
<th scope="row">{_("Store metrics")}</th>
|
||||
<td>
|
||||
<OnOffSwitch
|
||||
id="server-pmlogger-switch"
|
||||
state={this.pmlogger_service.state === "running"}
|
||||
disabled={this.pmlogger_service.state == "starting" || this.state.pm_logger_switch_disabled}
|
||||
onChange={this.onPmLoggerSwitchChange} />
|
||||
</td>
|
||||
</tr>}
|
||||
|
||||
{this.state.pcp_link_visible &&
|
||||
<tr>
|
||||
<th scope="row">{_("PCP")}</th>
|
||||
<td>
|
||||
<a id="system-configuration-enable-pcp-link" onClick={() => install_dialog("cockpit-pcp")}>
|
||||
<span className="pficon pficon-info" />
|
||||
<span>{_("Enable stored metrics…")}</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PageSystemInformationChangeHostname.prototype = {
|
||||
|
@ -749,17 +446,19 @@ PageSystemInformationChangeHostname.prototype = {
|
|||
},
|
||||
|
||||
enter: function() {
|
||||
var self = this;
|
||||
|
||||
self.hostname_proxy = PageSystemInformationChangeHostname.client.proxy();
|
||||
|
||||
self._initial_hostname = self.hostname_proxy.StaticHostname || "";
|
||||
self._initial_pretty_hostname = self.hostname_proxy.PrettyHostname || "";
|
||||
$("#sich-pretty-hostname").val(self._initial_pretty_hostname);
|
||||
$("#sich-hostname").val(self._initial_hostname);
|
||||
|
||||
this._always_update_from_pretty = false;
|
||||
this._update();
|
||||
this.client = cockpit.dbus('org.freedesktop.hostname1',
|
||||
{ superuser : "try" });
|
||||
this.hostname_proxy = this.client.proxy();
|
||||
|
||||
this.hostname_proxy.wait()
|
||||
.then(() => {
|
||||
this._initial_hostname = this.hostname_proxy.StaticHostname || "";
|
||||
this._initial_pretty_hostname = this.hostname_proxy.PrettyHostname || "";
|
||||
$("#sich-pretty-hostname").val(this._initial_pretty_hostname);
|
||||
$("#sich-hostname").val(this._initial_hostname);
|
||||
this._update();
|
||||
});
|
||||
},
|
||||
|
||||
show: function() {
|
||||
|
@ -771,13 +470,11 @@ PageSystemInformationChangeHostname.prototype = {
|
|||
},
|
||||
|
||||
_on_apply_button: function(event) {
|
||||
var self = this;
|
||||
|
||||
var new_full_name = $("#sich-pretty-hostname").val();
|
||||
var new_name = $("#sich-hostname").val();
|
||||
|
||||
var one = self.hostname_proxy.call("SetStaticHostname", [new_name, true]);
|
||||
var two = self.hostname_proxy.call("SetPrettyHostname", [new_full_name, true]);
|
||||
var one = this.hostname_proxy.call("SetStaticHostname", [new_name, true]);
|
||||
var two = this.hostname_proxy.call("SetPrettyHostname", [new_full_name, true]);
|
||||
|
||||
// We can't use Promise.all() here, because dialg expects a promise
|
||||
// with a progress() method (see pkg/lib/patterns.js)
|
||||
|
@ -1340,30 +1037,3 @@ PageSystemInformationChangeSystime.prototype = {
|
|||
function PageSystemInformationChangeSystime() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
$("#system_information_hardware_text").on("click", function() {
|
||||
$("#system_information_hardware_text").tooltip("hide");
|
||||
cockpit.jump("/system/hwinfo", cockpit.transport.host);
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#system-information-enable-pcp-link").on("click", function() {
|
||||
install_dialog("cockpit-pcp");
|
||||
});
|
||||
|
||||
function init() {
|
||||
var server_page;
|
||||
|
||||
cockpit.translate();
|
||||
|
||||
server_page = new PageServer();
|
||||
server_page.setup();
|
||||
|
||||
dialog_setup(new PageSystemInformationChangeHostname());
|
||||
dialog_setup(change_systime_dialog = new PageSystemInformationChangeSystime());
|
||||
|
||||
page_show(server_page);
|
||||
$("body").removeAttr("hidden");
|
||||
}
|
||||
|
||||
$(init);
|
|
@ -0,0 +1,8 @@
|
|||
#system_information_ssh_keys .list-group-item {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
#system_information_hostname_text + span {
|
||||
font: inherit;
|
||||
margin-left: 0.5em;
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardBody, CardFooter } from '@patternfly/react-core';
|
||||
|
||||
import cockpit from "cockpit";
|
||||
import { page_status } from "notifications";
|
||||
import { PageStatusNotifications } from "../page-status.jsx";
|
||||
import * as service from "service.js";
|
||||
|
||||
import "./healthCard.less";
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
|
||||
export class HealthCard extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { insightsLinkVisible: false };
|
||||
this.refresh_os_updates_state = this.refresh_os_updates_state.bind(this);
|
||||
this.refresh_insights_status = this.refresh_insights_status.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
page_status.addEventListener("changed", this.refresh_os_updates_state);
|
||||
this.refresh_os_updates_state();
|
||||
|
||||
this.insights_client_timer = service.proxy("insights-client.timer");
|
||||
this.insights_client_timer.addEventListener("changed", this.refresh_insights_status);
|
||||
this.refresh_insights_status();
|
||||
}
|
||||
|
||||
refresh_insights_status() {
|
||||
const subfeats = (cockpit.manifests.subscriptions && cockpit.manifests.subscriptions.features) || { };
|
||||
if (subfeats.insights && this.insights_client_timer.exists && !this.insights_client_timer.enabled)
|
||||
this.setState({ insightsLinkVisible: true });
|
||||
else
|
||||
this.setState({ insightsLinkVisible: false });
|
||||
}
|
||||
|
||||
refresh_os_updates_state() {
|
||||
const status = page_status.get("updates") || { };
|
||||
const details = status.details;
|
||||
|
||||
this.setState({
|
||||
updateDetails: details,
|
||||
updateStatus: status,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const pageStatusNotifications = React.createElement(PageStatusNotifications);
|
||||
const updateDetails = this.state.updateDetails || { };
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>{_("Health")}</CardHeader>
|
||||
<CardBody>
|
||||
<ul className="system-health-events">
|
||||
<li id="page_status_notifications">{pageStatusNotifications}</li>
|
||||
<li>
|
||||
<>
|
||||
{!!updateDetails.icon && <>
|
||||
<span id="system_information_updates_icon" className={updateDetails.icon || ""} />
|
||||
<a id="system_information_updates_text" onClick={() => cockpit.jump("/" + (updateDetails.link || "updates"))}>{updateDetails.text || this.state.updateStatus.title || ""}</a>
|
||||
</>}
|
||||
</>
|
||||
</li>
|
||||
{this.state.insightsLinkVisible && <li className="system-health-insights">
|
||||
<span className="pficon pficon-warning-triangle-o" />
|
||||
{ cockpit.manifests.subscriptions
|
||||
? <a id="insights_text" tabIndex='0' role="button" onClick={() => cockpit.jump("/subscriptions")}>{_("Not connected to Insights")}</a>
|
||||
: <span id="insights_text">{_("Not connected to Insights")}</span>}
|
||||
</li>}
|
||||
</ul>
|
||||
</CardBody>
|
||||
<CardFooter />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
.system-health {
|
||||
&-events {
|
||||
> li {
|
||||
// Better align system health icons
|
||||
display: flex;
|
||||
// Align icons vertically to text
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
|
||||
+ li {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.fa,
|
||||
.pficon {
|
||||
// Bump up icon size
|
||||
font-size: 1.25rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
> .fa,
|
||||
> .pficon {
|
||||
// Some icons are not the same width; give them a suggested width
|
||||
flex-basis: 1.25rem;
|
||||
display: flex;
|
||||
// Align icons to the center of the basic width
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
> :not(a):last-child {
|
||||
flex: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import $ from "jquery";
|
||||
|
||||
import cockpit from "cockpit";
|
||||
|
||||
import './motdCard.less';
|
||||
|
||||
export class MotdCard extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { motdVisible: false };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const self = this;
|
||||
|
||||
cockpit.file("/etc/motd").watch(function(content) {
|
||||
if (content)
|
||||
content = content.trimRight();
|
||||
if (content && content != cockpit.localStorage.getItem('dismissed-motd')) {
|
||||
self.setState({ motdText: content, motdVisible: true });
|
||||
} else {
|
||||
self.setState({ motdVisible: false });
|
||||
}
|
||||
// To help the tests known when we have loaded motd
|
||||
$('#motd-box').attr('data-stable', 'yes');
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.motdVisible)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<div id="motd-box" className="motd-box">
|
||||
<div className="pf-c-alert pf-m-info pf-m-inline" aria-label="Info alert">
|
||||
<div className="pf-c-alert__icon">
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</div>
|
||||
<h4 className="pf-c-alert__title">
|
||||
<pre id="motd">{this.state.motdText}</pre>
|
||||
</h4>
|
||||
<div className="pf-c-alert__action">
|
||||
<button className="pf-c-button pf-m-plain" type="button" onClick={() => {
|
||||
this.setState({ motdVisible: false });
|
||||
cockpit.localStorage.setItem('dismissed-motd', $('#motd').text());
|
||||
}}>
|
||||
<i className="fa fa-times" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#motd {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
white-space: pre-wrap;
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import cockpit from "cockpit";
|
||||
import * as service from "service.js";
|
||||
import $ from "jquery";
|
||||
|
||||
export function ServerTime() {
|
||||
var self = this;
|
||||
|
||||
var client = cockpit.dbus('org.freedesktop.timedate1');
|
||||
var timedate = client.proxy();
|
||||
|
||||
var time_offset = null;
|
||||
var remote_offset = null;
|
||||
|
||||
this.client = client;
|
||||
|
||||
self.timedate = timedate;
|
||||
|
||||
this.ntp_waiting_value = null;
|
||||
this.ntp_waiting_resolve = null;
|
||||
|
||||
self.timedate1_service = service.proxy("dbus-org.freedesktop.timedate1.service");
|
||||
self.timesyncd_service = service.proxy("systemd-timesyncd.service");
|
||||
|
||||
/*
|
||||
* The time we return from here as its UTC time set to the
|
||||
* server time. This is the only way to get predictable
|
||||
* behavior and formatting of a Date() object in the absence of
|
||||
* IntlDateFormat and friends.
|
||||
*/
|
||||
Object.defineProperty(self, 'utc_fake_now', {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
var offset = time_offset + remote_offset;
|
||||
return new Date(offset + (new Date()).valueOf());
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(self, 'now', {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return new Date(time_offset + (new Date()).valueOf());
|
||||
}
|
||||
});
|
||||
|
||||
self.format = function format(and_time) {
|
||||
var string = self.utc_fake_now.toISOString();
|
||||
if (!and_time)
|
||||
return string.split('T')[0];
|
||||
var pos = string.lastIndexOf(':');
|
||||
if (pos !== -1)
|
||||
string = string.substring(0, pos);
|
||||
return string.replace('T', ' ');
|
||||
};
|
||||
|
||||
self.updateInterval = window.setInterval(function() {
|
||||
$(self).triggerHandler("changed");
|
||||
}, 30000);
|
||||
|
||||
self.wait = function wait() {
|
||||
if (remote_offset === null)
|
||||
return self.update();
|
||||
return cockpit.resolve();
|
||||
};
|
||||
|
||||
self.update = function update() {
|
||||
return cockpit.spawn(["date", "+%s:%z"], { err: "message" })
|
||||
.done(function(data) {
|
||||
const parts = data.trim().split(":");
|
||||
const timems = parseInt(parts[0], 10) * 1000;
|
||||
let tzmin = parseInt(parts[1].slice(-2), 10);
|
||||
const tzhour = parseInt(parts[1].slice(0, -2));
|
||||
if (tzhour < 0)
|
||||
tzmin = -tzmin;
|
||||
const offsetms = (tzhour * 3600000) + tzmin * 60000;
|
||||
const now = new Date();
|
||||
time_offset = (timems - now.valueOf());
|
||||
remote_offset = offsetms;
|
||||
$(self).triggerHandler("changed");
|
||||
})
|
||||
.fail(function(ex) {
|
||||
console.log("Couldn't calculate server time offset: " + cockpit.message(ex));
|
||||
});
|
||||
};
|
||||
|
||||
self.change_time = function change_time(datestr, hourstr, minstr) {
|
||||
var dfd = $.Deferred();
|
||||
|
||||
/*
|
||||
* The browser is brain dead when it comes to dates. But even if
|
||||
* it wasn't, or we loaded a library like moment.js, there is no
|
||||
* way to make sense of this date without a round trip to the
|
||||
* server ... the timezone is really server specific.
|
||||
*/
|
||||
cockpit.spawn(["date", "--date=" + datestr + " " + hourstr + ":" + minstr, "+%s"])
|
||||
.fail(function(ex) {
|
||||
dfd.reject(ex);
|
||||
})
|
||||
.done(function(data) {
|
||||
var seconds = parseInt(data.trim(), 10);
|
||||
timedate.call('SetTime', [seconds * 1000 * 1000, false, true])
|
||||
.fail(function(ex) {
|
||||
dfd.reject(ex);
|
||||
})
|
||||
.done(function() {
|
||||
self.update();
|
||||
dfd.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return dfd;
|
||||
};
|
||||
|
||||
self.poll_ntp_synchronized = function poll_ntp_synchronized() {
|
||||
client.call(timedate.path,
|
||||
"org.freedesktop.DBus.Properties", "Get", ["org.freedesktop.timedate1", "NTPSynchronized"])
|
||||
.fail(function(error) {
|
||||
if (error.name != "org.freedesktop.DBus.Error.UnknownProperty" &&
|
||||
error.problem != "not-found")
|
||||
console.log("can't get NTPSynchronized property", error);
|
||||
})
|
||||
.done(function(result) {
|
||||
var ifaces = { "org.freedesktop.timedate1": { NTPSynchronized: result[0].v } };
|
||||
var data = { };
|
||||
data[timedate.path] = ifaces;
|
||||
client.notify(data);
|
||||
});
|
||||
};
|
||||
|
||||
self.ntp_updated = function ntp_updated(path, iface, member, args) {
|
||||
if (!self.ntp_waiting_resolve || !args[1].NTP)
|
||||
return;
|
||||
if (self.ntp_waiting_value !== args[1].NTP.v)
|
||||
console.warn("Unexpected value of NTP");
|
||||
self.ntp_waiting_resolve();
|
||||
self.ntp_waiting_resolve = null;
|
||||
};
|
||||
|
||||
self.close = function close() {
|
||||
client.close();
|
||||
};
|
||||
|
||||
self.update();
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardBody, CardFooter } from '@patternfly/react-core';
|
||||
|
||||
import cockpit from "cockpit";
|
||||
import * as machine_info from "machine-info.js";
|
||||
|
||||
import "./systemInformationCard.less";
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
|
||||
export class SystemInfomationCard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
this.getDMIInfo = this.getDMIInfo.bind(this);
|
||||
this.getMachineId = this.getMachineId.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getDMIInfo();
|
||||
this.getMachineId();
|
||||
}
|
||||
|
||||
getMachineId() {
|
||||
var machine_id = cockpit.file("/etc/machine-id");
|
||||
var self = this;
|
||||
|
||||
machine_id.read()
|
||||
.done(function(content) {
|
||||
self.setState({ machineID: content });
|
||||
})
|
||||
.fail(function(ex) {
|
||||
// FIXME show proper Alerts
|
||||
console.error("Error reading machine id", ex);
|
||||
})
|
||||
.always(function() {
|
||||
machine_id.close();
|
||||
});
|
||||
}
|
||||
|
||||
getDMIInfo() {
|
||||
var self = this;
|
||||
|
||||
machine_info.dmi_info()
|
||||
.then(function(fields) {
|
||||
let vendor = fields.sys_vendor;
|
||||
let name = fields.product_name;
|
||||
if (!vendor || !name) {
|
||||
vendor = fields.board_vendor;
|
||||
name = fields.board_name;
|
||||
}
|
||||
if (!vendor || !name)
|
||||
self.setState({ hardwareText: undefined });
|
||||
else
|
||||
self.setState({ hardwareText: vendor + " " + name });
|
||||
|
||||
self.setState({ assetTagText: fields.product_serial || fields.chassis_serial });
|
||||
}, function(ex) {
|
||||
// FIXME show proper Alerts
|
||||
console.debug("couldn't read dmi info: " + ex);
|
||||
self.setState({ assetTagText: undefined, hardwareText: undefined });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Card className="system-information">
|
||||
<CardHeader>{_("System information")}</CardHeader>
|
||||
<CardBody>
|
||||
<table className="pf-c-table pf-m-grid-md pf-m-compact">
|
||||
<tbody>
|
||||
{this.state.hardwareText && <tr>
|
||||
<th scope="row">{_("Model")}</th>
|
||||
<td>
|
||||
<div id="system_information_hardware_text">{this.state.hardwareText}</div>
|
||||
</td>
|
||||
</tr>}
|
||||
{this.state.assetTagText && <tr>
|
||||
<th scope="row">{_("Asset tag")}</th>
|
||||
<td>
|
||||
<div id="system_information_asset_tag_text">{this.state.assetTagText}</div>
|
||||
</td>
|
||||
</tr>}
|
||||
<tr>
|
||||
<th scope="row" className="system-information-machine-id">{_("Machine ID")}</th>
|
||||
<td>
|
||||
<div id="system_machine_id">{this.state.machineID}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
<a className="no-left-padding" onClick={() => cockpit.jump("/system/hwinfo", cockpit.transport.host)}>
|
||||
{_("View hardware details")}
|
||||
</a>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#system_machine_id {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.system-information {
|
||||
&-machine-id {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
Card, CardHeader, CardBody, CardFooter,
|
||||
Progress, ProgressMeasureLocation, ProgressVariant,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import * as machine_info from "machine-info.js";
|
||||
import cockpit from "cockpit";
|
||||
|
||||
import "./usageCard.less";
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
|
||||
const UPDATE_DELAY = 5000;
|
||||
|
||||
export class UsageCard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { pollingEnabled: true };
|
||||
this.updateMemoryInfo = this.updateMemoryInfo.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateMemoryInfo();
|
||||
|
||||
cockpit.addEventListener("visibilitychange", () => {
|
||||
this.setState((prevState, _) => ({ pollingEnabled: !prevState.pollingEnabled }));
|
||||
}, () => this.updateMemoryInfo());
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.setState({ pollingEnabled: false });
|
||||
}
|
||||
|
||||
updateMemoryInfo() {
|
||||
if (!this.state.pollingEnabled)
|
||||
return;
|
||||
|
||||
machine_info.cpu_ram_info().done(info => {
|
||||
this.setState({
|
||||
memTotal: Number((info.memory / (1024 * 1024 * 1024)).toFixed(1)),
|
||||
memAvailable: Number((info.available_memory / (1024 * 1024 * 1024)).toFixed(1))
|
||||
});
|
||||
});
|
||||
window.setTimeout(this.updateMemoryInfo.bind(this), UPDATE_DELAY);
|
||||
}
|
||||
|
||||
render() {
|
||||
const memUsed = Number((this.state.memTotal - this.state.memAvailable).toFixed(1));
|
||||
const fraction = memUsed / this.state.memTotal;
|
||||
|
||||
return (
|
||||
<Card className="system-usage">
|
||||
<CardHeader>{_("Usage")}</CardHeader>
|
||||
<CardBody>
|
||||
<table className="pf-c-table pf-m-grid-md pf-m-compact">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{_("Memory")}</th>
|
||||
<td>
|
||||
<Progress value={memUsed}
|
||||
className="pf-m-sm"
|
||||
min={0} max={Number(this.state.memTotal)}
|
||||
variant={fraction > 0.9 ? ProgressVariant.danger : ProgressVariant.info}
|
||||
label={cockpit.format(_("$0 GiB / $1 GiB"), memUsed, this.state.memTotal)}
|
||||
measureLocation={ProgressMeasureLocation.outside} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
<a className="no-left-padding" onClick={() => cockpit.jump("/system/graphs", cockpit.transport.host)}>
|
||||
{_("View graphs")}
|
||||
</a>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.system-usage {
|
||||
.pf-c-progress {
|
||||
/* FIXME */
|
||||
// This is a hacky, simple approach to make usage bars align.
|
||||
// If the text is too short, there's too much space on the right.
|
||||
// If the text is too long, it flows over and the bar is smaller.
|
||||
// It's better than doing nothing, however...
|
||||
// A more proper fix may require reworking the HTML a bit.
|
||||
grid-template-columns: 1fr minmax(50%, auto);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'polyfills.js';
|
||||
import cockpit from "cockpit";
|
||||
import $ from "jquery";
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {
|
||||
Page, PageSection, PageSectionVariants,
|
||||
Gallery, Button,
|
||||
Dropdown, DropdownItem, KebabToggle,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { shutdown, shutdown_modal_setup } from "./shutdown.js";
|
||||
|
||||
import { SystemInfomationCard } from './overview-cards/systemInformationCard.jsx';
|
||||
import { ConfigurationCard } from './overview-cards/configurationCard.jsx';
|
||||
import { HealthCard } from './overview-cards/healthCard.jsx';
|
||||
import { MotdCard } from './overview-cards/motdCard.jsx';
|
||||
import { UsageCard } from './overview-cards/usageCard.jsx';
|
||||
import { ServerTime } from './overview-cards/serverTime.js';
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
var permission = cockpit.permission({ admin: true });
|
||||
permission.addEventListener("changed", update_shutdown_privileged);
|
||||
|
||||
function update_shutdown_privileged() {
|
||||
$(".shutdown-privileged").update_privileged(
|
||||
permission, cockpit.format(
|
||||
_("The user <b>$0</b> is not permitted to shutdown or restart this server"),
|
||||
permission.user ? permission.user.name : ''),
|
||||
'bottom'
|
||||
);
|
||||
}
|
||||
|
||||
class OverviewPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
actionKebabIsOpen: false
|
||||
};
|
||||
this.onKebabToggle = actionKebabIsOpen => this.setState({ actionKebabIsOpen });
|
||||
this.onKebabSelect = event => this.setState({ actionKebabIsOpen: !this.state.actionKebabIsOpen });
|
||||
this.hostnameMonitor = this.hostnameMonitor.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.hostnameMonitor();
|
||||
shutdown_modal_setup();
|
||||
}
|
||||
|
||||
hostname_text() {
|
||||
if (!this.state.hostnameData)
|
||||
return undefined;
|
||||
|
||||
const pretty_hostname = this.state.hostnameData.PrettyHostname;
|
||||
const static_hostname = this.state.hostnameData.StaticHostname;
|
||||
let str = this.state.hostnameData.HostName;
|
||||
|
||||
if (pretty_hostname && static_hostname && static_hostname != pretty_hostname)
|
||||
str = pretty_hostname + " (" + static_hostname + ")";
|
||||
else if (static_hostname)
|
||||
str = static_hostname;
|
||||
|
||||
return str || '';
|
||||
}
|
||||
|
||||
hostnameMonitor() {
|
||||
this.client = cockpit.dbus('org.freedesktop.hostname1',
|
||||
{ superuser : "try" });
|
||||
this.hostname_proxy = this.client.proxy('org.freedesktop.hostname1',
|
||||
'/org/freedesktop/hostname1');
|
||||
this.hostname_proxy.addEventListener("changed", data => {
|
||||
this.setState({ hostnameData: data.detail });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { actionKebabIsOpen } = this.state;
|
||||
const dropdownItems = [
|
||||
<DropdownItem key="shutdown" onClick={() => shutdown('shutdown', new ServerTime())} component="button">
|
||||
{_("Shutdown")}
|
||||
</DropdownItem>,
|
||||
];
|
||||
return (
|
||||
<Page>
|
||||
<PageSection className='ct-overview-header' variant={PageSectionVariants.light}>
|
||||
<div className='ct-overview-header-hostname'>
|
||||
<h1>
|
||||
{this.hostname_text() || ""}
|
||||
</h1>
|
||||
{this.state.hostnameData &&
|
||||
this.state.hostnameData.OperatingSystemPrettyName &&
|
||||
<div className="ct-overview-header-subheading" id="system_information_os_text">{cockpit.format(_("running $0"), this.state.hostnameData.OperatingSystemPrettyName)}</div>}
|
||||
</div>
|
||||
<div className='ct-overview-header-actions'>
|
||||
<Button className="shutdown-privileged" id='restart-button' variant="secondary" onClick={() => shutdown('restart', new ServerTime())}>
|
||||
{_("Restart")}
|
||||
</Button>
|
||||
<Dropdown
|
||||
id="shutdown-group"
|
||||
className="shutdown-privileged"
|
||||
position="right"
|
||||
onSelect={this.onKebabSelect}
|
||||
toggle={<KebabToggle onToggle={this.onKebabToggle} />}
|
||||
isOpen={actionKebabIsOpen}
|
||||
isPlain
|
||||
dropdownItems={dropdownItems}
|
||||
/>
|
||||
</div>
|
||||
</PageSection>
|
||||
<PageSection variant={PageSectionVariants.default}>
|
||||
<Gallery className='ct-system-overview' gutter="lg">
|
||||
<MotdCard />
|
||||
<HealthCard />
|
||||
<UsageCard />
|
||||
<SystemInfomationCard />
|
||||
<ConfigurationCard hostname={this.hostname_text()} />
|
||||
</Gallery>
|
||||
</PageSection>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
cockpit.translate();
|
||||
ReactDOM.render(<OverviewPage />, document.getElementById("overview"));
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", init);
|
|
@ -0,0 +1,248 @@
|
|||
@import "../../node_modules/@patternfly/react-styles/css/components/Table/table.css";
|
||||
@import "./system-global.less";
|
||||
|
||||
/* System Time Modal dialog needs table.css */
|
||||
@import "../lib/table.css";
|
||||
|
||||
#overview {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ct-overview-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&,
|
||||
&-hostname {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&-actions,
|
||||
&-hostname {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&-hostname {
|
||||
align-items: baseline;
|
||||
flex: auto;
|
||||
|
||||
> h1 {
|
||||
padding-right: 1rem;
|
||||
font-size: var(--pf-global--FontSize--2xl);
|
||||
}
|
||||
}
|
||||
|
||||
&-actions {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-subheading {
|
||||
font-size: var(--pf-global--FontSize--xl);
|
||||
}
|
||||
}
|
||||
|
||||
.ct-system-overview {
|
||||
--card-width: 24rem;
|
||||
--pf-l-gallery--GridTemplateColumns: repeat(auto-fill, minmax(var(--card-width), 1fr));
|
||||
|
||||
.motd-box {
|
||||
grid-column: ~"1 / -1";
|
||||
}
|
||||
|
||||
.pf-c-card {
|
||||
&__header {
|
||||
font-size: var(--pf-global--FontSize--2xl);
|
||||
font-weight: var(--pf-global--FontWeight--normal);
|
||||
}
|
||||
|
||||
&__body {
|
||||
.fa,
|
||||
.pficon {
|
||||
+ a {
|
||||
/* Space out icons + links */
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
> .fa,
|
||||
> .pficon {
|
||||
/* Space out icons inside of links */
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child .pf-c-table:last-child tr:last-child {
|
||||
/* Remove the border of tables when it's the last item in a card and there isn't a card footer */
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
p {
|
||||
+ p,
|
||||
+ button {
|
||||
margin-top: calc(var(--pf-global--LineHeight--md) * 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
th {
|
||||
font-size: var(--pf-global--FontSize--sm);
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pf-c-progress {
|
||||
&__status {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
&-icon {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pf-m-compact {
|
||||
th, td {
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
/* Make the overview fit on very narrow screens like an iPhone SE */
|
||||
|
||||
.pf-c-page__main-section:not(.ct-overview-header) {
|
||||
/* Remove left and right padding for cards on narrow viewports */
|
||||
--pf-c-page__main-section--PaddingRight: 0;
|
||||
--pf-c-page__main-section--PaddingLeft: 0;
|
||||
--pf-c-page__main-section--PaddingTop: 0.5rem;
|
||||
--pf-c-page__main-section--PaddingBottom: 0.5rem;
|
||||
}
|
||||
|
||||
.ct-system-overview {
|
||||
/* Reduce spacing between items */
|
||||
--pf-l-gallery--m-gutter--GridGap: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.ct-system-overview {
|
||||
/* Allow cards to be narrower on very narrow viewports */
|
||||
--card-width: 15rem;
|
||||
}
|
||||
}
|
||||
|
||||
#machine_id {
|
||||
font-family: var(--pf-global--FontFamily--redhatfont--monospace);
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.pf-c-table tr > * {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.ct-inline-list .pf-c-list.pf-m-inline {
|
||||
display: inline-flex;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.realms-op-diagnostics {
|
||||
/* standard PF alerts have a wide margin */
|
||||
max-width: 550px;
|
||||
text-align: left;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.realms-op-error .realms-op-more-diagnostics {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.realms-op-leave-only-row .pf-c-alert {
|
||||
/* standard PF alerts have a wide margin */
|
||||
padding-left: 2ex;
|
||||
|
||||
button {
|
||||
margin: 1ex 0;
|
||||
}
|
||||
}
|
||||
|
||||
#realms-op-leave-toggle {
|
||||
font-weight: bold;
|
||||
/* leave some space between form and leave toggle */
|
||||
line-height: 5rem;
|
||||
}
|
||||
|
||||
.realms-op-wait-message {
|
||||
margin-left: 10px;
|
||||
float: left;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
#sich-note-1,
|
||||
#sich-note-2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.small-messages {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.systime-inline {
|
||||
.form-control {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
&:first-of-type .form-control {
|
||||
margin: 0 4px 0 0;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 214px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-inline {
|
||||
background: #f4f4f4;
|
||||
border: 1px solid #bababa;
|
||||
padding: 4px;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-top-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
.pficon-close,
|
||||
.fa-plus {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
padding: 4px;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pf-c-button.no-left-padding {
|
||||
padding-left: 0;
|
||||
}
|
|
@ -51,14 +51,15 @@ export class PageStatusNotifications extends React.Component {
|
|||
const page = "system/services";
|
||||
const status = page_status.get(page);
|
||||
if (status && status.type && status.title) {
|
||||
this.props.toggle_label(true);
|
||||
this.props.toggle_label && this.props.toggle_label(true);
|
||||
const jump = () => cockpit.jump("/" + page);
|
||||
return (
|
||||
<span>
|
||||
<span className={icon_class_for_type(status.type)} /> <button className="link-button" role="link" onClick={jump}>{status.title}</button>
|
||||
</span>);
|
||||
<>
|
||||
<span className={icon_class_for_type(status.type)} />
|
||||
<a role="button" tabIndex="0" onClick={jump}>{status.title}</a>
|
||||
</>);
|
||||
} else {
|
||||
this.props.toggle_label(false);
|
||||
this.props.toggle_label && this.props.toggle_label(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import { Button, Modal, OverlayTrigger, Tooltip, DropdownKebab, MenuItem } from
|
|||
import cockpit from "cockpit";
|
||||
import { OnOffSwitch } from "cockpit-components-onoff.jsx";
|
||||
|
||||
import './service-details.css';
|
||||
import './service-details.less';
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import "./timer.css";
|
||||
@import "./timer.less";
|
||||
@import '../lib/journal.css';
|
||||
@import "../lib/table.css";
|
||||
@import "./system-global.css";
|
||||
@import "./system-global.less";
|
||||
|
||||
#services-page {
|
||||
overflow-y: scroll;
|
||||
|
@ -213,8 +213,12 @@ table.systemd-unit-relationship-table td:first-child {
|
|||
width: 40%;
|
||||
}
|
||||
|
||||
.fa-exclamation-triangle {
|
||||
color: var(--pf-global--warning-color--100);
|
||||
.service-template input {
|
||||
width: 50em;
|
||||
}
|
||||
|
||||
.service-unit-failed {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.service-template input {
|
|
@ -24,7 +24,7 @@ import cockpit from "cockpit";
|
|||
import "patterns";
|
||||
import "bootstrap-datepicker/dist/js/bootstrap-datepicker";
|
||||
|
||||
import "./shutdown.css";
|
||||
import "./shutdown.less";
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
|
||||
|
@ -34,6 +34,9 @@ var server_time = null;
|
|||
/* The current operation */
|
||||
var operation = null;
|
||||
|
||||
/* The delay in the dialog */
|
||||
var delay = 0;
|
||||
|
||||
/* The entry point, shows the dialog */
|
||||
export function shutdown(op, st) {
|
||||
operation = op;
|
||||
|
@ -41,65 +44,71 @@ export function shutdown(op, st) {
|
|||
$('#shutdown-dialog').modal('show');
|
||||
}
|
||||
|
||||
$('#shutdown-dialog .shutdown-date').datepicker({
|
||||
autoclose: true,
|
||||
todayHighlight: true,
|
||||
format: 'yyyy-mm-dd',
|
||||
startDate: "today",
|
||||
});
|
||||
|
||||
$("#shutdown-dialog input")
|
||||
.on('focusout', update)
|
||||
.on('change', update);
|
||||
|
||||
/* The delay in the dialog */
|
||||
var delay = 0;
|
||||
$("#shutdown-dialog .dropdown li")
|
||||
.on("click", function(ev) {
|
||||
delay = $(this).attr("value");
|
||||
update();
|
||||
});
|
||||
|
||||
/* Prefilling the date if it's been set */
|
||||
var cached_date = null;
|
||||
$('#shutdown-dialog .shutdown-date')
|
||||
.on('focusin', function() {
|
||||
cached_date = $(this).val();
|
||||
})
|
||||
.on('focusout', function() {
|
||||
if ($(this).val().length === 0)
|
||||
$(this).val(cached_date);
|
||||
});
|
||||
|
||||
$("#shutdown-dialog").on("show.bs.modal", function(ev) {
|
||||
/* The date picker also triggers this event, since it is modal */
|
||||
if (ev.target.id !== "shutdown-dialog")
|
||||
return;
|
||||
|
||||
$("#shutdown-dialog textarea")
|
||||
.val("")
|
||||
.attr("placeholder", _("Message to logged in users"))
|
||||
.attr("rows", 5);
|
||||
|
||||
/* Track the value correctly */
|
||||
delay = $("#shutdown-dialog li:first-child").attr("value");
|
||||
|
||||
server_time.wait().then(function() {
|
||||
$('#shutdown-dialog .shutdown-date').val(server_time.format());
|
||||
$('#shutdown-dialog .shutdown-hours').val(server_time.utc_fake_now.getUTCHours());
|
||||
$('#shutdown-dialog .shutdown-minutes').val(server_time.utc_fake_now.getUTCMinutes());
|
||||
export function shutdown_modal_setup() {
|
||||
$('#shutdown-dialog .shutdown-date').datepicker({
|
||||
autoclose: true,
|
||||
todayHighlight: true,
|
||||
format: 'yyyy-mm-dd',
|
||||
startDate: "today",
|
||||
});
|
||||
|
||||
if (operation == 'shutdown') {
|
||||
$('#shutdown-dialog .modal-title').text(_("Shut Down"));
|
||||
$("#shutdown-dialog .btn-danger").text(_("Shut Down"));
|
||||
} else {
|
||||
$('#shutdown-dialog .modal-title').text(_("Restart"));
|
||||
$("#shutdown-dialog .btn-danger").text(_("Restart"));
|
||||
}
|
||||
$("#shutdown-dialog input")
|
||||
.on('focusout', update)
|
||||
.on('change', update);
|
||||
|
||||
update();
|
||||
});
|
||||
$("#shutdown-dialog .dropdown li")
|
||||
.on("click", function(ev) {
|
||||
delay = $(this).attr("value");
|
||||
update();
|
||||
});
|
||||
|
||||
/* Prefilling the date if it's been set */
|
||||
var cached_date = null;
|
||||
$('#shutdown-dialog .shutdown-date')
|
||||
.on('focusin', function() {
|
||||
cached_date = $(this).val();
|
||||
})
|
||||
.on('focusout', function() {
|
||||
if ($(this).val().length === 0)
|
||||
$(this).val(cached_date);
|
||||
});
|
||||
|
||||
$("#shutdown-dialog").on("show.bs.modal", function(ev) {
|
||||
/* The date picker also triggers this event, since it is modal */
|
||||
if (ev.target.id !== "shutdown-dialog")
|
||||
return;
|
||||
|
||||
$("#shutdown-dialog textarea")
|
||||
.val("")
|
||||
.attr("placeholder", _("Message to logged in users"))
|
||||
.attr("rows", 5);
|
||||
|
||||
/* Track the value correctly */
|
||||
delay = $("#shutdown-dialog li:first-child").attr("value");
|
||||
|
||||
server_time.wait().then(function() {
|
||||
$('#shutdown-dialog .shutdown-date').val(server_time.format());
|
||||
$('#shutdown-dialog .shutdown-hours').val(server_time.utc_fake_now.getUTCHours());
|
||||
$('#shutdown-dialog .shutdown-minutes').val(server_time.utc_fake_now.getUTCMinutes());
|
||||
});
|
||||
|
||||
if (operation == 'shutdown') {
|
||||
$('#shutdown-dialog .modal-title').text(_("Shut Down"));
|
||||
$("#shutdown-dialog .btn-danger").text(_("Shut Down"));
|
||||
} else {
|
||||
$('#shutdown-dialog .modal-title').text(_("Restart"));
|
||||
$("#shutdown-dialog .btn-danger").text(_("Restart"));
|
||||
}
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
/* Perform the action */
|
||||
|
||||
$("#shutdown-dialog .btn-danger").click(function() {
|
||||
$("#shutdown-dialog").dialog("promise", perform());
|
||||
});
|
||||
}
|
||||
|
||||
function update() {
|
||||
$("#shutdown-dialog input")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "../lib/page.css";
|
||||
@import '../lib/form-layout.less';
|
||||
@import "../../node_modules/@patternfly/react-styles/css/components/Alert/alert.css";
|
||||
|
||||
.pf-c-alert {
|
|
@ -30,10 +30,10 @@ class TestHWinfo(SeleniumTest):
|
|||
self.machine.execute(command=cmd, input=self.lscpu)
|
||||
self.machine.execute('sudo chmod a+x {}'.format(self.lscpu_file))
|
||||
self.login()
|
||||
self.click(self.wait_link('System', cond=clickable))
|
||||
self.click(self.wait_link('Overview', cond=clickable))
|
||||
self.wait_frame("localhost/system")
|
||||
|
||||
self.click(self.wait_id("system_information_hardware_text"))
|
||||
self.click(self.wait_link("View hardware details"))
|
||||
self.mainframe()
|
||||
self.wait_frame("localhost/system/hwinfo")
|
||||
self.wait_id("hwinfo")
|
||||
|
|
|
@ -18,13 +18,13 @@ class NavigateTestSuite(SeleniumTest):
|
|||
def testNavigateNoReload(self):
|
||||
self.login()
|
||||
# Bring up a dialog on system page
|
||||
self.click(self.wait_link('System', cond=clickable))
|
||||
self.click(self.wait_link('Overview', cond=clickable))
|
||||
self.wait_frame("system")
|
||||
self.click(self.wait_id('system_information_systime_button', cond=clickable))
|
||||
self.wait_id('system_information_change_systime', cond=visible)
|
||||
|
||||
# Check hardware info page
|
||||
self.click(self.wait_id('system_information_hardware_text', cond=clickable))
|
||||
self.click(self.wait_link('View hardware details', cond=clickable))
|
||||
self.mainframe()
|
||||
self.wait_frame("hwinfo")
|
||||
self.wait_text('BIOS date')
|
||||
|
@ -37,6 +37,6 @@ class NavigateTestSuite(SeleniumTest):
|
|||
self.mainframe()
|
||||
|
||||
# Now navigate back to system page
|
||||
self.click(self.wait_link('System', cond=clickable))
|
||||
self.click(self.wait_link('Overview', cond=clickable))
|
||||
self.wait_frame("system")
|
||||
self.wait_id('system_information_change_systime', cond=visible)
|
||||
|
|
|
@ -26,14 +26,14 @@ class TunedProfiles(SeleniumTest):
|
|||
return self.machine.execute("/usr/sbin/tuned-adm active", quiet=True).strip().rsplit(" ", 1)[1]
|
||||
|
||||
def testPerformaceProfiles(self):
|
||||
self.click(self.wait_link('System', cond=clickable))
|
||||
self.click(self.wait_link('Overview', cond=clickable))
|
||||
self.wait_frame("system")
|
||||
self.click(self.wait_text(self.balanced_profile, cond=clickable))
|
||||
self.wait_text("Change Performance Profile")
|
||||
self.click(self.wait_text(self.desktop_profile, element="p", cond=clickable))
|
||||
self.click(self.wait_text("Change Profile", element="button", cond=clickable))
|
||||
self.wait_text("Change Performance Profile", cond=invisible)
|
||||
self.wait_id("server", cond=visible)
|
||||
self.wait_id("overview", cond=visible)
|
||||
self.wait_text(self.desktop_profile, cond=clickable)
|
||||
self.assertIn(self.desktop_profile, self.get_profile())
|
||||
|
||||
|
@ -42,6 +42,6 @@ class TunedProfiles(SeleniumTest):
|
|||
self.click(self.wait_text(self.balanced_profile, element="p", cond=clickable))
|
||||
self.click(self.wait_text("Change Profile", element="button", cond=clickable))
|
||||
self.wait_text("Change Performance Profile", cond=invisible)
|
||||
self.wait_id("server", cond=visible)
|
||||
self.wait_id("overview", cond=visible)
|
||||
self.wait_text(self.balanced_profile, cond=clickable)
|
||||
self.assertIn(self.balanced_profile, self.get_profile())
|
||||
|
|
|
@ -39,7 +39,10 @@ class TestActivePages(MachineCase):
|
|||
# /playground/preloaded, /system/services, and /updates
|
||||
n_extra_preloaded = 3
|
||||
|
||||
b.wait_present("#server")
|
||||
if m.image in [ "rhel-8-1-distropkg" ]:
|
||||
b.wait_present("#server")
|
||||
else:
|
||||
b.wait_present("#overview")
|
||||
|
||||
def showPagesAssertCount(count):
|
||||
b.switch_to_top()
|
||||
|
|
|
@ -521,8 +521,11 @@ class TestConnection(MachineCase):
|
|||
m.wait_for_cockpit_running('127.0.0.90', 9999)
|
||||
# System frame should work directly, no login page
|
||||
out = m.execute("curl --compressed http://127.0.0.90:9999/cockpit/@localhost/system/index.html")
|
||||
self.assertIn('id="system_machine_id"', out)
|
||||
self.assertIn('data-action="shutdown"', out)
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
self.assertIn('id="system_machine_id"', out)
|
||||
self.assertIn('data-action="shutdown"', out)
|
||||
else:
|
||||
self.assertIn('id="overview"', out)
|
||||
|
||||
# shut it down, wait until it is gone
|
||||
m.execute("pkill -ef cockpit-ws")
|
||||
|
@ -539,16 +542,21 @@ G_MESSAGES_DEBUG=all XDG_CONFIG_DIRS=/usr/local %s -p 9999 -a 127.0.0.90 --local
|
|||
|
||||
# System frame should work directly, no login page
|
||||
out = m.execute("curl --compressed http://127.0.0.90:9999/cockpit/@localhost/system/index.html")
|
||||
self.assertIn('id="system_machine_id"', out)
|
||||
self.assertIn('data-action="shutdown"', out)
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
self.assertIn('id="system_machine_id"', out)
|
||||
self.assertIn('data-action="shutdown"', out)
|
||||
else:
|
||||
self.assertIn('id="overview"', out)
|
||||
|
||||
self.allow_journal_messages("couldn't register polkit authentication agent.*")
|
||||
|
||||
@skipImage("OSTree doesn't have cockpit-ws", "fedora-coreos")
|
||||
@skipImage("Kernel does not allow user namespaces", "debian-stable", "debian-testing")
|
||||
def testCockpitDesktop(self):
|
||||
m = self.machine
|
||||
|
||||
cases = [(['/cockpit/@localhost/system/index.html', 'system', 'system/index', 'system/'],
|
||||
['id="system_machine_id"', 'data-action="shutdown"']
|
||||
['id="system_machine_id"' if m.image == "rhel-8-1-distropkg" else 'id="overview"']
|
||||
),
|
||||
(['/cockpit/@localhost/network/firewall.html', 'network/firewall'],
|
||||
['div id="firewall"', 'script src="firewall.js"']
|
||||
|
@ -558,8 +566,6 @@ G_MESSAGES_DEBUG=all XDG_CONFIG_DIRS=/usr/local %s -p 9999 -a 127.0.0.90 --local
|
|||
),
|
||||
]
|
||||
|
||||
m = self.machine
|
||||
|
||||
if "debian" in m.image or "ubuntu" in m.image:
|
||||
cockpit_desktop = "/usr/lib/cockpit/cockpit-desktop"
|
||||
else:
|
||||
|
|
|
@ -212,6 +212,9 @@ class TestBasicDashboard(MachineCase, DashBoardHelpers):
|
|||
|
||||
b.enter_page("/system", "10.111.113.3")
|
||||
b.wait_text_not("#system_information_systime_button", "")
|
||||
if m.image not in ["rhel-8-1-distropkg"]:
|
||||
b.click(".system-usage a") # View graphs
|
||||
b.enter_page("/system/graphs", "10.111.113.3")
|
||||
b.click("#link-network a")
|
||||
b.enter_page("/network", "10.111.113.3")
|
||||
|
||||
|
|
|
@ -375,9 +375,13 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group
|
|||
m.start_cockpit()
|
||||
b.login_and_go("/system", user="unpriv")
|
||||
# not an admin
|
||||
b.wait_present("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
b.wait_present("#shutdown-group button.disabled")
|
||||
b.wait_not_present("#shutdown-group button:not(.disabled)")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_present("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
b.wait_present("#shutdown-group button.disabled")
|
||||
b.wait_not_present("#shutdown-group button:not(.disabled)")
|
||||
else:
|
||||
b.wait_present("#restart-button.disabled[data-stable=yes]")
|
||||
b.wait_present("#shutdown-group.disabled[data-stable=yes]")
|
||||
b.logout()
|
||||
self.allow_authorize_journal_messages()
|
||||
# not allowed to restricted users
|
||||
|
@ -396,7 +400,10 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group
|
|||
m.execute("semanage login -a -s sysadm_u admin")
|
||||
b.login_and_go("/system")
|
||||
# shutdown button should be enabled and working
|
||||
b.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
else:
|
||||
b.click("#restart-button[data-stable=yes]")
|
||||
b.wait_popup("shutdown-dialog")
|
||||
b.wait_in_text("#shutdown-dialog .btn-danger", 'Restart')
|
||||
b.click('#shutdown-dialog button[data-dismiss="modal"]')
|
||||
|
|
|
@ -147,11 +147,17 @@ class TestMultiMachineAdd(MachineCase):
|
|||
|
||||
def testBasic(self):
|
||||
b = self.browser
|
||||
m = self.machine
|
||||
m2 = self.machine2
|
||||
m3 = self.machine3
|
||||
m3_host = "10.111.113.3:2222"
|
||||
change_ssh_port(m3, "10.111.113.3", 2222)
|
||||
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
hostname_selector = "#system_information_hostname_button"
|
||||
else:
|
||||
hostname_selector = "#system_information_hostname_text"
|
||||
|
||||
self.login_and_go(None)
|
||||
add_machine(b, "10.111.113.2")
|
||||
add_machine(b, m3_host)
|
||||
|
@ -176,7 +182,7 @@ class TestMultiMachineAdd(MachineCase):
|
|||
b.switch_to_top()
|
||||
b.wait_js_cond('window.location.pathname != "/dashboard"')
|
||||
b.enter_page("/system", host="10.111.113.2")
|
||||
b.wait_text_not("#system_information_hostname_button", "")
|
||||
b.wait_text_not(hostname_selector, "")
|
||||
b.switch_to_top()
|
||||
b.go("/dashboard")
|
||||
b.enter_page("/dashboard")
|
||||
|
@ -186,7 +192,7 @@ class TestMultiMachineAdd(MachineCase):
|
|||
b.switch_to_top()
|
||||
b.wait_js_cond('window.location.pathname != "/dashboard"')
|
||||
b.enter_page("/system", host=m3_host)
|
||||
b.wait_text_not("#system_information_hostname_button", "")
|
||||
b.wait_text_not(hostname_selector, "")
|
||||
b.switch_to_top()
|
||||
b.go("/dashboard")
|
||||
b.enter_page("/dashboard")
|
||||
|
@ -243,6 +249,11 @@ class TestMultiMachine(MachineCase):
|
|||
|
||||
name = self.machine.execute("hostname")
|
||||
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
hostname_selector = "#system_information_hostname_button"
|
||||
else:
|
||||
hostname_selector = "#system_information_hostname_text"
|
||||
|
||||
# Change os-release pretty name on m2
|
||||
m2.execute("sed -i '/NAME=.*/d' /etc/os-release")
|
||||
m2.execute("echo 'NAME=\"A Pretty Name\"' >> /etc/os-release")
|
||||
|
@ -250,7 +261,7 @@ class TestMultiMachine(MachineCase):
|
|||
b.switch_to_top()
|
||||
b.go("{}/system".format(root))
|
||||
b.enter_page("/system")
|
||||
b.wait_in_text('#system_information_hostname_button', name.strip())
|
||||
b.wait_in_text(hostname_selector, name.strip())
|
||||
b.switch_to_top()
|
||||
b.wait_js_cond('window.location.pathname == "{0}system"'.format(root))
|
||||
b.click("a[href='/dashboard']")
|
||||
|
@ -271,7 +282,7 @@ class TestMultiMachine(MachineCase):
|
|||
b.click('#login-button')
|
||||
b.expect_load()
|
||||
b.enter_page("/system")
|
||||
b.wait_in_text('#system_information_hostname_button', "machine2")
|
||||
b.wait_in_text(hostname_selector, "machine2")
|
||||
b.switch_to_top()
|
||||
|
||||
# Branding uses m2 pretty name
|
||||
|
@ -367,7 +378,7 @@ class TestMultiMachine(MachineCase):
|
|||
b.click('#login-button')
|
||||
b.expect_load()
|
||||
b.enter_page("/system")
|
||||
b.wait_in_text('#system_information_hostname_button', "machine2")
|
||||
b.wait_in_text(hostname_selector, "machine2")
|
||||
b.logout()
|
||||
|
||||
# Check hostkey isn't saved
|
||||
|
@ -421,7 +432,7 @@ class TestMultiMachine(MachineCase):
|
|||
b.click('#login-button')
|
||||
b.expect_load()
|
||||
b.enter_page("/system")
|
||||
b.wait_in_text('#system_information_hostname_button', "machine2")
|
||||
b.wait_in_text(hostname_selector, "machine2")
|
||||
b.switch_to_top()
|
||||
b.wait_js_cond('window.location.pathname == "{0}=10.111.113.2/system"'.format(root))
|
||||
|
||||
|
@ -464,6 +475,11 @@ class TestMultiMachine(MachineCase):
|
|||
b = self.browser
|
||||
m = self.machine
|
||||
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
hostname_selector = "#system_information_hostname_button"
|
||||
else:
|
||||
hostname_selector = "#system_information_hostname_text"
|
||||
|
||||
m.execute('mkdir -p /etc/cockpit/ && echo "[WebService]\nUrlRoot = cockpit-new" > /etc/cockpit/cockpit.conf')
|
||||
m.start_cockpit()
|
||||
|
||||
|
@ -499,7 +515,7 @@ class TestMultiMachine(MachineCase):
|
|||
b.enter_page("/dashboard")
|
||||
add_machine(b, "10.111.113.2")
|
||||
b.enter_page("/system", host="10.111.113.2")
|
||||
b.wait_text("#system_information_hostname_button", "machine2")
|
||||
b.wait_text(hostname_selector, "machine2")
|
||||
b.switch_to_top()
|
||||
b.wait_js_cond('window.location.pathname == "/cockpit-new/@10.111.113.2/system"')
|
||||
|
||||
|
|
|
@ -166,7 +166,10 @@ class TestUpdates(PackageCase):
|
|||
b.wait_text("#system_information_updates_text", "Bug Fix Updates Available")
|
||||
self.assertIn("fa-bug", b.attr("#system_information_updates_icon", "class"))
|
||||
# should be a link, click on it to go to /updates
|
||||
b.click("#system_information_updates_text a")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.click("#system_information_updates_text a")
|
||||
else:
|
||||
b.click("#system_information_updates_text")
|
||||
b.enter_page("/updates")
|
||||
|
||||
# old versions are still installed
|
||||
|
@ -329,7 +332,10 @@ class TestUpdates(PackageCase):
|
|||
self.assertIn("security", b.attr("#system_information_updates_icon", "class"))
|
||||
|
||||
# should be a link, click on it to go to back to /updates
|
||||
b.click("#system_information_updates_text a")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.click("#system_information_updates_text a")
|
||||
else:
|
||||
b.click("#system_information_updates_text")
|
||||
b.enter_page("/updates")
|
||||
|
||||
# install only security updates
|
||||
|
@ -737,7 +743,10 @@ class TestUpdatesSubscriptions(PackageCase):
|
|||
b.wait_in_text("#system_information_updates_text", "Not Registered")
|
||||
self.assertIn("triangle", b.attr("#system_information_updates_icon", "class"))
|
||||
# should be a link leading to subscriptions page
|
||||
b.click("#system_information_updates_text a")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.click("#system_information_updates_text a")
|
||||
else:
|
||||
b.click("#system_information_updates_text")
|
||||
b.enter_page("/subscriptions")
|
||||
|
||||
# software updates page also shows unregistered
|
||||
|
|
|
@ -82,7 +82,7 @@ OnCalendar=daily
|
|||
b.switch_to_top()
|
||||
b.click("a[href='/system']")
|
||||
b.enter_page("/system")
|
||||
b.wait_present("#system_information_hostname_button")
|
||||
b.wait_present("#system_information_systime_button")
|
||||
b.switch_to_top()
|
||||
b.click("a[href='/system/services']")
|
||||
b.enter_page("/system/services")
|
||||
|
@ -150,7 +150,10 @@ OnCalendar=daily
|
|||
# Check that the system page is translated
|
||||
b.go("/system")
|
||||
b.enter_page("/system")
|
||||
b.wait_in_text("#server", "Neustarten")
|
||||
if m.image in ["rhel-8-1-distropkg"]:
|
||||
b.wait_in_text("#server", "Neustarten")
|
||||
else:
|
||||
b.wait_in_text(".ct-overview-header", "Neustarten")
|
||||
|
||||
# Systemd timer localization
|
||||
b.go("/system/services")
|
||||
|
@ -219,7 +222,7 @@ OnCalendar=daily
|
|||
pages = ["/system", "/system/logs", "/network", "/users", "/system/services", "/system/terminal"]
|
||||
|
||||
self.login_and_go('/system')
|
||||
b.wait_present('#server')
|
||||
b.wait_present('#overview')
|
||||
|
||||
b.switch_to_top()
|
||||
b.click('#content-user-name')
|
||||
|
@ -285,7 +288,10 @@ OnCalendar=daily
|
|||
m.execute('locale-gen pt_BR && locale-gen pt_BR.UTF-8 && update-locale')
|
||||
|
||||
self.login_and_go('/system')
|
||||
b.wait_present('#server')
|
||||
if m.image in ["rhel-8-1-distropkg"]:
|
||||
b.wait_present('#server')
|
||||
else:
|
||||
b.wait_present('#overview')
|
||||
b.switch_to_top()
|
||||
b.click('#content-user-name')
|
||||
b.click('.display-language-menu a')
|
||||
|
@ -300,7 +306,10 @@ OnCalendar=daily
|
|||
# Check that the system page is translated
|
||||
b.go('/system')
|
||||
b.enter_page('/system')
|
||||
b.wait_in_text('#server', 'Reiniciar')
|
||||
if m.image in ["rhel-8-1-distropkg"]:
|
||||
b.wait_in_text('#server', 'Reiniciar')
|
||||
else:
|
||||
b.wait_in_text('.ct-overview-header', 'Reiniciar')
|
||||
|
||||
# Systemd timer localization
|
||||
b.go('/system/services')
|
||||
|
@ -378,7 +387,10 @@ OnCalendar=daily
|
|||
|
||||
self.login_and_go()
|
||||
|
||||
b.wait_in_text("#host-apps", "System")
|
||||
if m.image in ["rhel-8-1-distropkg"]: # Changed in #12265
|
||||
b.wait_in_text("#host-apps", "System")
|
||||
else:
|
||||
b.wait_in_text("#host-apps", "Overview")
|
||||
m.execute("mkdir -p /home/admin/.local/share/cockpit/foo")
|
||||
m.write("/home/admin/.local/share/cockpit/foo/manifest.json",
|
||||
'{ "menu": { "index": { "label": "FOO!" } } }')
|
||||
|
@ -412,7 +424,7 @@ OnCalendar=daily
|
|||
# Check that any substring work
|
||||
b.focus("#filter-menus")
|
||||
b.set_val("#filter-menus", "CoUN")
|
||||
b.wait_not_present("#sidebar-menu > li > a > span:contains('System')")
|
||||
b.wait_not_present("#sidebar-menu > li > a > span:contains('Overview')")
|
||||
b.wait_present("#sidebar-menu > li > a > span:contains('Accounts')")
|
||||
b.wait_text("#sidebar-menu > li > a > span:contains('Accounts') mark", "coun")
|
||||
|
||||
|
@ -472,7 +484,7 @@ OnCalendar=daily
|
|||
b.focus("#filter-menus")
|
||||
b.set_val("#filter-menus", "firew")
|
||||
b.wait_present("#sidebar-menu > li > a > span:contains('Networking')")
|
||||
b.wait_not_present("#sidebar-menu > li > a > span:contains('System')")
|
||||
b.wait_not_present("#sidebar-menu > li > a > span:contains('Overview')")
|
||||
b.click("#sidebar-menu > li > a > span:contains('Networking')")
|
||||
b.enter_page("/network/firewall")
|
||||
|
||||
|
@ -489,7 +501,7 @@ OnCalendar=daily
|
|||
b.wait_present("#content")
|
||||
b.go("/system")
|
||||
b.enter_page("/system")
|
||||
b.wait_in_text("#server", "Neustarten")
|
||||
b.wait_in_text(".ct-overview-header", "Neustarten")
|
||||
|
||||
b.switch_to_top()
|
||||
b.wait_present("#sidebar-menu > li > a > span:contains('Dienste')")
|
||||
|
|
|
@ -121,9 +121,14 @@ class TestRealms(MachineCase):
|
|||
|
||||
# when joined to a domain, changing the hostname is fatal, so should be disabled
|
||||
b.wait_present("#system_information_hostname_button[disabled]")
|
||||
b.mouse("#system_information_hostname_button", "mouseover")
|
||||
b.wait_in_text(".tooltip-inner", "Host name should not be changed in a domain")
|
||||
b.mouse("#system_information_hostname_button", "mouseout")
|
||||
if m.image in ["rhel-8-1-distropkg"]:
|
||||
b.mouse("#system_information_hostname_button", "mouseover")
|
||||
b.wait_in_text(".tooltip-inner", "Host name should not be changed in a domain")
|
||||
b.mouse("#system_information_hostname_button", "mouseout")
|
||||
else:
|
||||
b.mouse("#system_information_hostname_tooltip", "mouseover")
|
||||
b.wait_in_text(".tooltip-inner", "Host name should not be changed in a domain")
|
||||
b.mouse("#system_information_hostname_tooltip", "mouseout")
|
||||
b.wait_not_present(".tooltip-inner")
|
||||
|
||||
# should not have any leftover tickets from the joining
|
||||
|
@ -326,7 +331,11 @@ class TestRealms(MachineCase):
|
|||
b.enter_page('/system')
|
||||
# shutdown button should be enabled and working
|
||||
# it takes a while for the permission check to finish, it is always enabled at first
|
||||
b.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
if m.image in ["rhel-8-1-distropkg"]:
|
||||
b.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
else:
|
||||
b.click("#overview #restart-button")
|
||||
|
||||
b.wait_popup("shutdown-dialog")
|
||||
b.wait_in_text("#shutdown-dialog .btn-danger", 'Restart')
|
||||
b.click("#shutdown-dialog .dropdown button")
|
||||
|
|
|
@ -58,9 +58,13 @@ class TestShutdownRestart(MachineCase):
|
|||
b.click('#login-button')
|
||||
b.expect_load()
|
||||
b.enter_page("/system")
|
||||
b.wait_present("#shutdown-group button[data-action=restart].disabled")
|
||||
b.wait_present("#shutdown-group button.dropdown-toggle.disabled")
|
||||
b.wait_not_present("#shutdown-group button:not(.disabled)")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_present("#shutdown-group button[data-action=restart].disabled")
|
||||
b.wait_present("#shutdown-group button.dropdown-toggle.disabled")
|
||||
b.wait_not_present("#shutdown-group button:not(.disabled)")
|
||||
else:
|
||||
b.wait_present("#restart-button.disabled")
|
||||
b.wait_present("#restart-button + div.pf-c-dropdown.disabled")
|
||||
|
||||
b.logout()
|
||||
|
||||
|
@ -72,7 +76,10 @@ class TestShutdownRestart(MachineCase):
|
|||
time.sleep(2)
|
||||
|
||||
# shutdown button should be enabled and working
|
||||
b.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
else:
|
||||
b.click("#restart-button")
|
||||
b.wait_popup("shutdown-dialog")
|
||||
b.wait_in_text("#shutdown-dialog .btn-danger", 'Restart')
|
||||
b.click("#shutdown-dialog .dropdown button")
|
||||
|
@ -96,7 +103,10 @@ class TestShutdownRestart(MachineCase):
|
|||
self.login_and_go("/system")
|
||||
|
||||
# Reboot
|
||||
b.click("#shutdown-group button[data-action=restart]")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
else:
|
||||
b.click("#restart-button")
|
||||
b.wait_popup("shutdown-dialog")
|
||||
b.wait_in_text("#shutdown-dialog .btn-danger", 'Restart')
|
||||
b.click("#shutdown-dialog .dropdown button")
|
||||
|
@ -132,10 +142,16 @@ class TestShutdownRestart(MachineCase):
|
|||
b2.click('#troubleshoot-dialog .btn-primary')
|
||||
b2.wait_popdown('troubleshoot-dialog')
|
||||
b2.enter_page("/system", host="10.111.113.1")
|
||||
b2.wait_text("#system_information_hostname_button", "machine1")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b2.wait_text("#system_information_hostname_button", "machine1")
|
||||
else:
|
||||
b2.wait_text("#system_information_hostname_text", "machine1")
|
||||
|
||||
# Check auto reconnect on restart
|
||||
b2.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b2.click("#shutdown-group button[data-action=restart][data-stable=yes]")
|
||||
else:
|
||||
b2.click("#restart-button")
|
||||
b2.wait_popup("shutdown-dialog")
|
||||
b2.wait_in_text("#shutdown-dialog .btn-danger", 'Restart')
|
||||
b2.click("#shutdown-dialog .dropdown button")
|
||||
|
@ -157,9 +173,10 @@ class TestShutdownRestart(MachineCase):
|
|||
b.enter_page("/system")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.click("#shutdown-group span.caret")
|
||||
b.click("#shutdown-group a:contains('Shut Down')")
|
||||
else:
|
||||
b.click("#shutdown-group i.fa-caret-down")
|
||||
b.click("#shutdown-group a:contains('Shut Down')")
|
||||
b.click("#shutdown-group .pf-c-dropdown__toggle") # kebab
|
||||
b.click("#shutdown-group button:contains('Shutdown')")
|
||||
b.wait_popup("shutdown-dialog")
|
||||
b.click("#shutdown-dialog .dropdown button")
|
||||
b.click("a:contains('Specific Time')")
|
||||
|
|
|
@ -193,11 +193,14 @@ class TestSystemInfo(MachineCase):
|
|||
b.wait_not_in_text("#system_information_ssh_keys .list-group", old_alt)
|
||||
b.wait_in_text("#system_information_ssh_keys .list-group", new_alt)
|
||||
|
||||
b.wait_text('#system_information_os_text',
|
||||
b.wait_in_text('#system_information_os_text',
|
||||
"Foobar Adventure Linux Server 2.0 (Day of Doom)")
|
||||
|
||||
m.execute("hostnamectl set-hostname --static --pretty 'Adventure Box'")
|
||||
b.wait_in_text('#system_information_hostname_button', "Adventure Box")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_in_text('#system_information_hostname_button', "Adventure Box")
|
||||
else:
|
||||
b.wait_in_text('#system_information_hostname_text', "Adventure Box")
|
||||
|
||||
b.click('#system_information_hostname_button')
|
||||
b.wait_popup("system_information_change_hostname")
|
||||
|
@ -206,13 +209,10 @@ class TestSystemInfo(MachineCase):
|
|||
b.click("#system_information_change_hostname button:contains('Change')")
|
||||
b.wait_popdown("system_information_change_hostname")
|
||||
|
||||
if m.image in ["fedora-coreos"]:
|
||||
# rpm-ostree version looks like 30.20191014.0, we don't check out a custom OSTree on the image
|
||||
b.wait_in_text('#system-ostree-version-link', ".20")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_in_text('#system_information_hostname_button', "Adventure Box (host1.cockpit.lan)")
|
||||
else:
|
||||
b.wait_not_visible("#system-ostree-version-link")
|
||||
|
||||
b.wait_text('#system_information_hostname_button', "Adventure Box (host1.cockpit.lan)")
|
||||
b.wait_in_text('#system_information_hostname_text', "Adventure Box (host1.cockpit.lan)")
|
||||
self.assertEqual(m.execute("hostname").strip(), "host1.cockpit.lan")
|
||||
|
||||
b.logout()
|
||||
|
@ -353,8 +353,10 @@ class TestSystemInfo(MachineCase):
|
|||
m.execute("rm -f /etc/motd")
|
||||
|
||||
self.login_and_go("/system")
|
||||
b.wait_present('#motd-box[data-stable=yes]')
|
||||
b.wait_not_visible('#motd-box')
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_not_visible('#motd-box')
|
||||
else:
|
||||
b.wait_not_present('#motd-box')
|
||||
|
||||
m.execute(r"printf ' Hello\n World\n\n' >/etc/motd")
|
||||
b.wait_visible('#motd-box')
|
||||
|
@ -368,13 +370,19 @@ class TestSystemInfo(MachineCase):
|
|||
b.click('#motd-box button.close')
|
||||
else:
|
||||
b.click('#motd-box button.pf-c-button')
|
||||
b.wait_not_visible('#motd-box')
|
||||
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_not_visible('#motd-box')
|
||||
else:
|
||||
b.wait_not_present('#motd-box')
|
||||
|
||||
# motd should stay dismissed after a reload
|
||||
b.reload()
|
||||
b.enter_page("/system")
|
||||
b.wait_present('#motd-box[data-stable=yes]')
|
||||
b.wait_not_visible('#motd-box')
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_not_visible('#motd-box')
|
||||
else:
|
||||
b.wait_not_present('#motd-box')
|
||||
|
||||
m.execute("echo Hello again >/etc/motd")
|
||||
b.wait_visible('#motd-box')
|
||||
|
@ -389,7 +397,12 @@ class TestSystemInfo(MachineCase):
|
|||
|
||||
self.login_and_go("/system")
|
||||
b.wait_in_text('#system_information_hardware_text', "QEMU")
|
||||
b.click('#system_information_hardware_text')
|
||||
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
hardware_page_link = '#system_information_hardware_text'
|
||||
else:
|
||||
hardware_page_link = '.system-information a'
|
||||
b.click(hardware_page_link)
|
||||
b.enter_page("/system/hwinfo")
|
||||
|
||||
# system info
|
||||
|
@ -451,12 +464,16 @@ class TestSystemInfo(MachineCase):
|
|||
b.logout()
|
||||
self.machine.execute("mount -t tmpfs none /sys/class/dmi/id")
|
||||
self.login_and_go("/system")
|
||||
b.wait_present('#system_information_hardware_text')
|
||||
# asset tag should be hidden
|
||||
b.wait_not_visible('#system_information_asset_tag_text')
|
||||
# Hardware can just be a generic link
|
||||
b.wait_text('#system_information_hardware_text', "Details")
|
||||
b.click("#system_information_hardware_text")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_not_visible('#system_information_asset_tag_text')
|
||||
# Hardware can just be a generic link
|
||||
b.wait_text('#system_information_hardware_text', "Details")
|
||||
else:
|
||||
b.wait_not_present('#system_information_asset_tag_text')
|
||||
# Hardware should be hidden
|
||||
b.wait_not_present('#system_information_hardware_text')
|
||||
b.click(hardware_page_link)
|
||||
b.enter_page("/system/hwinfo")
|
||||
|
||||
# CPU should still be shown, but not the DMI fields
|
||||
|
@ -475,14 +492,17 @@ class TestSystemInfo(MachineCase):
|
|||
m.write("/sys/class/dmi/id/chassis_type", "10")
|
||||
b.go("/system")
|
||||
b.enter_page('/system')
|
||||
b.wait_text('#system_information_hardware_text', "Details")
|
||||
if m.image == "rhel-8-1-distropkg":
|
||||
b.wait_text('#system_information_hardware_text', "Details")
|
||||
else:
|
||||
b.wait_not_present('#system_information_hardware_text')
|
||||
|
||||
m.write("/sys/class/dmi/id/board_vendor", "VENDOR")
|
||||
m.write("/sys/class/dmi/id/board_name", "NAME")
|
||||
b.reload()
|
||||
b.enter_page('/system')
|
||||
b.wait_in_text('#system_information_hardware_text', "VENDOR NAME")
|
||||
b.click("#system_information_hardware_text")
|
||||
b.click(hardware_page_link)
|
||||
b.enter_page("/system/hwinfo")
|
||||
b.wait_in_text('#hwinfo .info-table-ct tbody:nth-of-type(1) tr:nth-of-type(2) td', "NAME")
|
||||
b.wait_in_text('#hwinfo .info-table-ct tbody:nth-of-type(1) tr:nth-of-type(3) td', "VENDOR")
|
||||
|
@ -718,7 +738,7 @@ fi
|
|||
b.wait_text("#insights_text", "Not connected to Insights")
|
||||
|
||||
m.execute("systemctl enable insights-client.timer")
|
||||
b.wait_not_visible("#insights_text")
|
||||
b.wait_not_present("#insights_text")
|
||||
|
||||
class TestPcp(packagelib.PackageCase):
|
||||
|
||||
|
@ -730,8 +750,8 @@ class TestPcp(packagelib.PackageCase):
|
|||
# the OSTree images don't have pcp and can't install additional software
|
||||
if m.ostree_image:
|
||||
self.login_and_go("/system")
|
||||
b.wait_not_visible("#server-pmlogger-switch")
|
||||
b.wait_not_visible("#system-information-enable-pcp-link")
|
||||
b.wait_not_present("#server-pmlogger-switch")
|
||||
b.wait_not_present("#system-configuration-enable-pcp-link")
|
||||
return
|
||||
|
||||
m.execute("pkcon remove -y pcp")
|
||||
|
@ -748,14 +768,23 @@ class TestPcp(packagelib.PackageCase):
|
|||
|
||||
# the offer to install it should be visible
|
||||
self.login_and_go("/system")
|
||||
b.wait_not_visible("#server-pmlogger-switch")
|
||||
b.wait_in_text("#system-information-enable-pcp-link", "Enable stored metrics…")
|
||||
b.click("#system-information-enable-pcp-link")
|
||||
if m.image in ["rhel-8-1-distropkg"]:
|
||||
b.wait_not_visible("#server-pmlogger-switch")
|
||||
b.wait_in_text("#system-information-enable-pcp-link", "Enable stored metrics...")
|
||||
b.click("#system-information-enable-pcp-link")
|
||||
else:
|
||||
b.wait_not_present("#server-pmlogger-switch")
|
||||
b.wait_in_text("#system-configuration-enable-pcp-link", "Enable stored metrics...")
|
||||
b.click("#system-configuration-enable-pcp-link")
|
||||
b.click(".modal-footer button.btn-primary:contains('Install')")
|
||||
b.wait_not_present(".modal-dialog:contains('Install Software')")
|
||||
|
||||
|
||||
b.wait_visible("#server-pmlogger-switch")
|
||||
b.wait_not_visible("#system-information-enable-pcp-link")
|
||||
if m.image in ["rhel-8-1-distropkg"]:
|
||||
b.wait_not_visible("#system-information-enable-pcp-link")
|
||||
else:
|
||||
b.wait_not_present("#system-configuration-enable-pcp-link")
|
||||
|
||||
# Turn stored metrics on
|
||||
b.wait_present("#server-pmlogger-switch input:not(:checked)")
|
||||
|
|
|
@ -103,22 +103,23 @@ var info = {
|
|||
|
||||
"systemd/services": [
|
||||
"systemd/init.js",
|
||||
"systemd/services.css",
|
||||
"systemd/services.less",
|
||||
],
|
||||
"systemd/logs": [
|
||||
"systemd/logs.js",
|
||||
"systemd/logs.less",
|
||||
],
|
||||
"systemd/system": [
|
||||
"systemd/host.js",
|
||||
"systemd/host.css",
|
||||
"systemd/overview": [
|
||||
"systemd/overview.jsx",
|
||||
"systemd/overview.less",
|
||||
],
|
||||
"systemd/terminal": [
|
||||
"systemd/terminal.jsx",
|
||||
"systemd/terminal.css",
|
||||
"systemd/terminal.less",
|
||||
],
|
||||
"systemd/hwinfo": [
|
||||
"systemd/hwinfo.jsx",
|
||||
"systemd/hwinfo.css",
|
||||
"systemd/hwinfo.less",
|
||||
],
|
||||
"systemd/graphs": [
|
||||
"systemd/graphs.js",
|
||||
|
|
Loading…
Reference in New Issue