Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F11722796
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/assets/js/salt-config.js b/src/assets/js/salt-config.js
index ea72592..dbd0d0f 100644
--- a/src/assets/js/salt-config.js
+++ b/src/assets/js/salt-config.js
@@ -1,383 +1,391 @@
/* -------------------------------------------------------------
Nasqueron infrastructure
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project: Nasqueron
Author: SΓ©bastien Santoro aka Dereckson
Dependencies: jQuery
Filename: salt-config.js
Licence: CC-BY 4.0, MIT, BSD-2-Clause (multi-licensing)
------------------------------------------------------------- */
/* -------------------------------------------------------------
Table of contents
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
:: Servers list
:: States
:: Code to run when document is ready
*/
const ServersConfig = function (container) {
/* -------------------------------------------------------------
States
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const States = function (container, serverName, serverHost) {
const states = {
///
/// Constants
///
SALT_BASE_URL: "https://devcentral.nasqueron.org/source/operations/browse/main/",
SALT_STAGING_URL: "https://devcentral.nasqueron.org/source/staging/browse/master/",
SALT_DOC_STATES_URL: "https://docs.saltproject.io/en/latest/ref/states/all/",
///
/// Private properties
///
/**
* A JQuery selector expression to a DOM element to publish to.
*
* @var string
*/
container: "",
server: "",
///
/// Constructor
///
/**
* Initializes an instance of this object.
*
* @param container The DOM element JQuery selector where to write
* @param serverName The name of the server, to display it
* @param serverHost The FQDN of the server, to fetch config data
*/
load: function (container, serverName, serverHost) {
this.container = container;
this.server = serverName;
this.refreshData(serverHost);
},
///
/// Main methods
///
refreshData: function (serverHost) {
let url = "https://" + serverHost + "/datasources/infra/all-states.json";
$.getJSON(url, function (configurationStates) {
states.refreshUI(configurationStates);
});
},
refreshUI: function (configurationStates) {
$(this.container).html(this.formatConfig(configurationStates));
$("#config-back-to-server-list").on("click", function () {
console.log("Back to servers list");
new ServersList(container)
})
},
formatConfig: function (states) {
return `
<button id="config-back-to-server-list" class="button extra-action">Β« Back to servers list</button>
<h2 class="config-server">${this.server}</h2>
${this.formatStates(states)}`
},
formatState: function (name, state) {
let output = '<div class="state">'
output += '<div class="state-name">' + name + "</div>"
for (const [key, properties] of Object.entries(state)) {
if (key.startsWith("__")) {
continue
}
output += `<div class="state-module">
${this.resolveSaltModuleMethod(key, properties)}
</div>`
output += '<div class="state-properties">'
for (const property of properties) {
if (typeof property === "string") {
// Method is already parsed by extractMethod
continue
}
if (property.order !== undefined) {
// We're lucky we already receive the states in the
// sorted order, so we can ignore this.
continue
}
output += this.dump(property)
}
output += "</div>"
}
output += "</div>"
return output
},
formatStates: function (server_states) {
let current_unit = ""
let output = '<div class="states">'
let roles_output = ""
let roles = []
for (const [role, role_states] of Object.entries(server_states)) {
roles.push(role)
roles_output += `<div class="config-role">
<h3 id="${this.makeId(role)}" class="config-role-title">${role}</h3>
<div class="config-role-content">`
if ($.isEmptyObject(role_states)) {
roles_output += '<p class="config-error">No information gathered for this role. There is probably an error in Salt configuration.</p>';
}
for (const [name, individual_state] of Object.entries(role_states)) {
// Gets unit from the state source SLS to generate units headings
let unit = individual_state["__sls__"].replace(role + ".", "")
if (unit !== current_unit) {
roles_output += `<h4 class="config-unit">${unit}</h4>`
current_unit = unit
}
roles_output += this.formatState(name, individual_state)
}
roles_output += "</div></div>";
}
roles_output += '</div>';
output += `
<div class="config-summary-roles">
<h3 class="config-summary-roles-heading">Roles assigned</h3>
<ul class="config-summary-roles-list">
`
for (const role of roles) {
output += `
<li class="config-summary-role">
<a href="#${this.makeId(role)}">${role}</a>
</li>
`
}
output += "</ul></div>"
output += roles_output;
return output;
},
makeId: function (expression) {
return expression.replace("/", ".")
},
resolveSaltModuleMethod: function (module, properties) {
const method = this.extractMethod(properties);
const link = `${this.SALT_DOC_STATES_URL}salt.states.${module}.html#salt.states.${module}.${method}`
return `<a class="salt-link" href="${link}">${module}.${method}</a>`
},
extractMethod: function (properties) {
for (const property of properties) {
if (typeof property === "string") {
return property
}
}
},
isInStagingRepo: url => url.startsWith("salt://software/") || url.startsWith("salt://wwwroot/"),
resolveSaltLink: function (url) {
const base = this.isInStagingRepo(url)
? this.SALT_STAGING_URL
: this.SALT_BASE_URL
const link = base + url.replace("salt://", "")
return `<a class="salt-link" href="${link}">${url}</a>`
},
// roles/core/rc/files/periodic.conf
dump: function (data) {
if (data === null) {
return `<span class="null">NULL</span>`
}
if (typeof data === "string" && data.startsWith("salt://")) {
return this.resolveSaltLink(data)
}
+ if (typeof data === "string" && data.startsWith("credential for ")) {
+ return `<span class="scalar credential">${data}</span>`
+ }
+
+ if (typeof data === "string" && data.startsWith("random credential")) {
+ return `<span class="scalar credential random">${data}</span>`
+ }
+
if (this.isScalar(data)) {
return `<span class="scalar">${data}</span>`
}
if (typeof data === "object") {
if (data.constructor.name === "Array") {
return this.dumpArray(data);
}
return this.dumpObject(data);
}
},
dumpArray: function (values) {
let dumped = '<ul class="state-list">'
for (const value of values) {
dumped += `<li class="state-list-item">${this.dump(value)}</li>`
}
dumped += '</ul>'
return dumped
},
dumpObject: function (data) {
let dumped = ""
for (const [key, value] of Object.entries(data)) {
dumped += `
<div class="state-property">
<span class="key">${key}</span>
<span class="value">${this.dump(value)}</span>
</div>
`
}
return dumped
},
isScalar: value => typeof value === "boolean"
|| typeof value === "number"
|| typeof value === "string"
};
states.load(container, serverName, serverHost)
return states;
};
/* -------------------------------------------------------------
Servers list
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const ServersList = function (container) {
const serversList = {
///
/// Constants
///
SERVERS_API_URL: "https://api.nasqueron.org/infra/servers.json",
///
/// Private properties
///
/**
* A JQuery selector expression to a DOM element to publish to.
*
* @var string
*/
container: "",
servers: undefined,
///
/// Constructor
///
/**
* Initializes an instance of this object.
*
* @param container The DOM element JQuery selector where to write
*/
load: function (container) {
this.container = container;
this.refreshData();
},
///
/// Data model
///
fetchServers: function () {
let that = this
$.getJSON(this.SERVERS_API_URL, function (servers) {
that.servers = servers
that.refreshUI()
})
},
refreshData: function () {
this.fetchServers();
},
///
/// UI representation
///
refreshUI: function () {
$(this.container).html(this.formatData())
for (const server of $(".server")) {
$(server).on("click", function () {
new States(container, server.id, server.dataset.hostname)
})
}
},
formatData: function () {
let output = '<h2>Servers</h2><ul class="servers">';
for (const [server, properties] of Object.entries(this.servers)) {
if (properties.configurator !== "salt") {
continue;
}
output += `
<li class="server" id="${server}" data-hostname="${properties.hostname}">
<span class="server-property server-name">${properties.name}</span>
<span class="server-property server-description">${properties.description}</span>
</li>
`
}
output += "</ul>"
return output;
}
};
serversList.load(container);
return serversList;
};
/* -------------------------------------------------------------
Initialization
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
new ServersList(container)
}
/* -------------------------------------------------------------
Code to run when document is ready
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
$(document).ready(function() {
new ServersConfig("#config");
});
diff --git a/src/assets/scss/components/_salt-config.scss b/src/assets/scss/components/_salt-config.scss
index f640707..8f82197 100644
--- a/src/assets/scss/components/_salt-config.scss
+++ b/src/assets/scss/components/_salt-config.scss
@@ -1,150 +1,166 @@
/* -------------------------------------------------------------
General elements
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
.extra-action {
float: right;
}
/* -------------------------------------------------------------
Servers list
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
.servers {
li {
list-style-type: none;
padding: 1em;
border: solid 2px $primary-color;
width: 16em;
float: left;
margin-right: 1em;
margin-bottom: 1em;
.server-property {
display: block;
}
.server-name {
color: $secondary-color;
font-weight: bold;
}
}
li:hover {
background-color: lighten($body-background, 10%);
//background-color: #474747;
cursor: zoom-in;
}
}
/* -------------------------------------------------------------
Salt configuration
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
$config-margin: 1em;
$icon-width: 3.5rem;
$config-block-margin-height: 2.5em;
.config-server::before {
content: "π₯οΈ ";
display: inline-block;
width: $icon-width;
}
.config-summary-roles {
margin-bottom: $config-block-margin-height;
}
.config-summary-roles-heading::before {
content: "π ";
display: inline-block;
width: $icon-width;
}
.config-role {
margin-bottom: $config-block-margin-height;
line-height: 1.5em;
.config-role-title::before {
content: "π¦ ";
display: inline-block;
width: $icon-width;
}
.config-role-content {
a {
color: #b5c9c7;
}
a:hover {
color: white;
}
.state {
margin-bottom: 1.25em;
.state-name {
color: #c4e3e9;
font-weight: bold;
}
.state-module {
margin-left: $config-margin;
}
.state-properties {
margin-left: 2 * $config-margin;
.state-property {
.key {
color: #d2eaee;
}
.key::after {
content: ": "
}
.value .state-property {
padding-left: $config-margin;
}
}
ul.state-list {
margin-bottom: 0;
}
.state-list-item {
margin-left: $config-margin / 2;
.state-property {
padding-left: 0 !important;
}
}
}
}
}
.config-error {
font-weight: bold;
}
- .config-error, .null {
+ .config-error, .null, .credential {
color: $warning-color;
}
.scalar {
white-space: pre;
}
.null::before {
content: "β ";
}
+ .credential::before {
+ content: "π ";
+ }
+
+ .random::before {
+ content: "π² ";
+ }
+
+ .credential::before {
+ content: "π ";
+ }
+
+ .random.credential::before {
+ content: "π²π ";
+ }
+
.config-error::before {
content: "π₯ ";
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Sep 18, 05:22 (15 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2990056
Default Alt Text
(16 KB)
Attached To
Mode
rINFRAWWW Nasqueron infrastructure servers website
Attached
Detach File
Event Timeline
Log In to Comment