Page MenuHomeDevCentral

D2557.id6461.diff
No OneTemporary

D2557.id6461.diff

diff --git a/config.yml b/config.yml
--- a/config.yml
+++ b/config.yml
@@ -35,3 +35,4 @@
- "src/assets/js/app.js"
- "src/assets/js/docker-registry.js"
- "src/assets/js/servers-log.js"
+ - "src/assets/js/salt-config.js"
diff --git a/src/assets/js/salt-config.js b/src/assets/js/salt-config.js
new file mode 100644
--- /dev/null
+++ b/src/assets/js/salt-config.js
@@ -0,0 +1,378 @@
+/* -------------------------------------------------------------
+ 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 (typeof data === "string" && data.startsWith("salt://")) {
+ return this.resolveSaltLink(data)
+ }
+
+ if (this.isScalar(data)) {
+ return data
+ }
+ 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/app.scss b/src/assets/scss/app.scss
--- a/src/assets/scss/app.scss
+++ b/src/assets/scss/app.scss
@@ -52,3 +52,4 @@
@import 'components/layout';
@import 'components/footer';
@import 'components/utilities-classes';
+@import 'components/salt-config';
\ No newline at end of file
diff --git a/src/assets/scss/components/_salt-config.scss b/src/assets/scss/components/_salt-config.scss
new file mode 100644
--- /dev/null
+++ b/src/assets/scss/components/_salt-config.scss
@@ -0,0 +1,139 @@
+/* -------------------------------------------------------------
+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 {
+ color: $warning-color;
+ font-weight: bold;
+ }
+
+ .config-error::before {
+ content: "🔥 ";
+ }
+
+}
\ No newline at end of file
diff --git a/src/pages/config/index.html b/src/pages/config/index.html
new file mode 100644
--- /dev/null
+++ b/src/pages/config/index.html
@@ -0,0 +1,18 @@
+---
+title: Servers configuration
+app: salt-config
+---
+
+<section class="row">
+ <div class="large-12 columns">
+ {{> config-intro}}
+ </div>
+</section>
+
+<div class="row">
+ <div class="large-9 columns" id="config">
+ </div>
+ <div class="large-3 columns">
+ {{> config-help}}
+ </div>
+</div>
diff --git a/src/partials/config/config-help.html b/src/partials/config/config-help.html
new file mode 100644
--- /dev/null
+++ b/src/partials/config/config-help.html
@@ -0,0 +1,18 @@
+<div class="callout primary">
+ <h3>Source</h3>
+ <p>These entries are compiled from the <a href="https://devcentral.nasqueron.org/source/operations/">Nasqueron Operations repository</a>.</p>
+ <p>They describe the state of the server, as known by Salt.</p>
+</div>
+<div class="callout primary">
+ <h3>Change config</h3>
+ <p>
+ To amend the server configuration, you need to commit it to <a href="https://devcentral.nasqueron.org/source/operations/">rOPS</a>.
+ </p>
+ <p>The <a href="https://agora.nasqueron.org/Operations_grimoire">operations grimoire</a> contains help how to do so.</p>
+</div>
+<div class="callout primary">
+ <h3>License</h3>
+ <p>Individual entries are too short to be original, and so are in the public domain.</p>
+ <p>When original enough, content is available under <a rel="license" href="https://creativecommons.org/licenses/by/4.0/">CC-BY 4.0</a> licence.</p>
+ <p>This configuration, as a database, is made available under the <a rel="license" href="https://www.opendatacommons.org/licenses/pddl/1.0/">Public Domain Dedication and License v1.0</a>.</p>
+</div>
diff --git a/src/partials/config/config-intro.html b/src/partials/config/config-intro.html
new file mode 100644
--- /dev/null
+++ b/src/partials/config/config-intro.html
@@ -0,0 +1,4 @@
+<div class="callout">
+ <p><strong>Nasqueron infrastructure servers</strong> support our budding community of creative people, writers, developers and thinkers.</p>
+ <p>According our transparency principle, our <strong>servers configuration</strong> is open and auditable.</p>
+</div>
diff --git a/src/partials/default-layout/footer.html b/src/partials/default-layout/footer.html
--- a/src/partials/default-layout/footer.html
+++ b/src/partials/default-layout/footer.html
@@ -11,7 +11,8 @@
<div class="large-3 columns">
<dl>
<dt>Ops repositories</dt>
- <dd><a href="https://devcentral.nasqueron.org/source/operations/">Operations</a></dd>
+ <dd><a href="https://devcentral.nasqueron.org/source/operations/">Operations</a> →
+ <a href="/config">Config</a></dd>
<dd><a href="https://devcentral.nasqueron.org/diffusion/query/NelStiRVmgP0/">Docker images</a></dd>
<dt>Site repositories</dt>

File Metadata

Mime Type
text/plain
Expires
Thu, Dec 19, 06:09 (5 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2306163
Default Alt Text
D2557.id6461.diff (18 KB)

Event Timeline