Compare commits
3 Commits
8a0baa5bc9
...
880f1ccb01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
880f1ccb01 | ||
|
|
76c4d002a0 | ||
|
|
2260deee01 |
@@ -1,7 +1,7 @@
|
|||||||
// Modules
|
// Modules
|
||||||
const { SlashCommandBuilder } = require('discord.js');
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
const { DebugBuilder } = require("../utilities/debugBuilder");
|
const { DebugBuilder } = require("../utilities/debugBuilder");
|
||||||
const { filterAutocompleteValues } = require("../utilities/utils");
|
const { filterAutocompleteValues, filterPresetsAvailable } = require("../utilities/utils");
|
||||||
const { getOnlineNodes, getAllConnections } = require("../utilities/mysqlHandler");
|
const { getOnlineNodes, getAllConnections } = require("../utilities/mysqlHandler");
|
||||||
const { joinServerWrapper } = require("../controllers/adminController");
|
const { joinServerWrapper } = require("../controllers/adminController");
|
||||||
|
|
||||||
@@ -28,18 +28,7 @@ module.exports = {
|
|||||||
recordResolve(nodeRows);
|
recordResolve(nodeRows);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
log.DEBUG("Node objects: ", nodeObjects);
|
const options = await filterPresetsAvailable(nodeObjects);
|
||||||
var presetsAvailable = [];
|
|
||||||
for (const nodeObject of nodeObjects) {
|
|
||||||
log.DEBUG("Node object: ", nodeObject);
|
|
||||||
presetsAvailable.push.apply(presetsAvailable, nodeObject.presets);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.DEBUG("All Presets available: ", presetsAvailable);
|
|
||||||
|
|
||||||
// Remove duplicates
|
|
||||||
options = [...new Set(presetsAvailable)];
|
|
||||||
log.DEBUG("DeDuped Presets available: ", options);
|
|
||||||
|
|
||||||
// Filter the results to what the user is entering
|
// Filter the results to what the user is entering
|
||||||
filterAutocompleteValues(interaction, options);
|
filterAutocompleteValues(interaction, options);
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ a {
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
box-shadow: 0 0.46875rem 2.1875rem rgba(90, 97, 105, 0.1), 0 0.9375rem 1.40625rem rgba(90, 97, 105, 0.1), 0 0.25rem 0.53125rem rgba(90, 97, 105, 0.12), 0 0.125rem 0.1875rem rgba(90, 97, 105, 0.1);
|
box-shadow: 0 0.46875rem 2.1875rem rgba(90, 97, 105, 0.1), 0 0.9375rem 1.40625rem rgba(90, 97, 105, 0.1), 0 0.25rem 0.53125rem rgba(90, 97, 105, 0.12), 0 0.125rem 0.1875rem rgba(90, 97, 105, 0.1);
|
||||||
|
min-height: 85%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card .card-statistic .card-icon-large .bi {
|
.info-card .card-statistic .card-icon-large .bi {
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
const { getAllNodes, getNodeInfoFromId } = require("../utilities/mysqlHandler");
|
const { getAllNodes, getNodeInfoFromId, getAllConnections } = require("../utilities/mysqlHandler");
|
||||||
|
const { filterPresetsAvailable } = require("../utilities/utils");
|
||||||
|
|
||||||
/* GET home page. */
|
/* GET home page. */
|
||||||
router.get('/', (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
//var sources = libCore.getSources();
|
var nodes = await new Promise((recordResolve, recordReject) => {
|
||||||
return res.render('index', {'page': 'index'});
|
getAllNodes((nodeRows) => {
|
||||||
|
recordResolve(nodeRows);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var connections = await getAllConnections();
|
||||||
|
var presets = await new Promise((recordResolve, recordReject) => {
|
||||||
|
getAllNodes((nodeRows) => {
|
||||||
|
recordResolve(filterPresetsAvailable(nodeRows));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//var sources = libCore.getSources();
|
||||||
|
return res.render('index', { 'page': 'index', 'nodes': nodes, 'connections': connections, 'presets': presets });
|
||||||
});
|
});
|
||||||
|
|
||||||
/* GET node controller page. */
|
/* GET node controller page. */
|
||||||
@@ -18,7 +31,7 @@ router.get('/controller', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//var sources = libCore.getSources();
|
//var sources = libCore.getSources();
|
||||||
return res.render('controller', {'nodes' : nodes, 'page': 'controller'});
|
return res.render('controller', { 'nodes': nodes, 'page': 'controller' });
|
||||||
});
|
});
|
||||||
|
|
||||||
/* GET individual node page. */
|
/* GET individual node page. */
|
||||||
@@ -26,7 +39,7 @@ router.get('/node/:id', async (req, res) => {
|
|||||||
var node = await getNodeInfoFromId(req.params.id);
|
var node = await getNodeInfoFromId(req.params.id);
|
||||||
|
|
||||||
//var sources = libCore.getSources();
|
//var sources = libCore.getSources();
|
||||||
return res.render('node', {'node' : node, 'page': 'node'});
|
return res.render('node', { 'node': node, 'page': 'node' });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ exports.BufferToJson = (buffer) => {
|
|||||||
* @returns {string} The sanitized preset name to be used elsewhere
|
* @returns {string} The sanitized preset name to be used elsewhere
|
||||||
*/
|
*/
|
||||||
exports.SanitizePresetName = (presetName) => {
|
exports.SanitizePresetName = (presetName) => {
|
||||||
return String(presetName).toLowerCase().replace(/[\W_]+/g,"-")
|
return String(presetName).toLowerCase().replace(/[\W_]+/g, "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,11 +32,11 @@ exports.SanitizePresetName = (presetName) => {
|
|||||||
* @param interaction Discord interaction object
|
* @param interaction Discord interaction object
|
||||||
* @param param0.roleName {OPTIONAL} The role name to check the members in; Defaults to 'Bots'
|
* @param param0.roleName {OPTIONAL} The role name to check the members in; Defaults to 'Bots'
|
||||||
*/
|
*/
|
||||||
exports.getMembersInRole = async (interaction, roleName = "Bots" ) => {
|
exports.getMembersInRole = async (interaction, roleName = "Bots") => {
|
||||||
log.DEBUG("Fetching all members");
|
log.DEBUG("Fetching all members");
|
||||||
var guild = await interaction.client.guilds.fetch({ guild: interaction.guild.id, cache: false }); //cache all members in the server
|
var guild = await interaction.client.guilds.fetch({ guild: interaction.guild.id, cache: false }); //cache all members in the server
|
||||||
await guild.members.fetch({cache: false});
|
await guild.members.fetch({ cache: false });
|
||||||
await guild.roles.fetch({cache: false});
|
await guild.roles.fetch({ cache: false });
|
||||||
log.VERBOSE("Guild: ", guild);
|
log.VERBOSE("Guild: ", guild);
|
||||||
const role = await guild.roles.cache.find(role => role.name === roleName); //the role to check
|
const role = await guild.roles.cache.find(role => role.name === roleName); //the role to check
|
||||||
log.DEBUG("Role to check members from: ", role);
|
log.DEBUG("Role to check members from: ", role);
|
||||||
@@ -66,7 +66,7 @@ exports.getKeyByArrayValue = (object, value) => {
|
|||||||
if (typeof value == "string") return Object.keys(object).find(key => object[key].includes(value));
|
if (typeof value == "string") return Object.keys(object).find(key => object[key].includes(value));
|
||||||
const valueKey = Object.keys(value)[0];
|
const valueKey = Object.keys(value)[0];
|
||||||
return Object.keys(object).find(key => (object[key][valueKey] == value[valueKey]));
|
return Object.keys(object).find(key => (object[key][valueKey] == value[valueKey]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check to see if the input is a valid JSON string
|
* Check to see if the input is a valid JSON string
|
||||||
@@ -91,7 +91,7 @@ exports.isJsonString = (str) => {
|
|||||||
exports.getAllClientIds = () => {
|
exports.getAllClientIds = () => {
|
||||||
const jsonClientIds = JSON.parse(readFileSync(path.resolve(__dirname, '../clientIds.json')));
|
const jsonClientIds = JSON.parse(readFileSync(path.resolve(__dirname, '../clientIds.json')));
|
||||||
var clientObjects = [];
|
var clientObjects = [];
|
||||||
for (const jsonClientId of Object.keys(jsonClientIds)){
|
for (const jsonClientId of Object.keys(jsonClientIds)) {
|
||||||
clientObjects.push(new clientObject({
|
clientObjects.push(new clientObject({
|
||||||
_discord_id: jsonClientId,
|
_discord_id: jsonClientId,
|
||||||
_name: jsonClientIds[jsonClientId].name,
|
_name: jsonClientIds[jsonClientId].name,
|
||||||
@@ -110,7 +110,7 @@ exports.getAllClientIds = () => {
|
|||||||
exports.getClientObjectByClientID = (clientId) => {
|
exports.getClientObjectByClientID = (clientId) => {
|
||||||
const clientObjects = this.getAllClientIds();
|
const clientObjects = this.getAllClientIds();
|
||||||
log.DEBUG("All client IDs: ", clientObjects);
|
log.DEBUG("All client IDs: ", clientObjects);
|
||||||
for (const clientObject of clientObjects){
|
for (const clientObject of clientObjects) {
|
||||||
if (clientObject.clientId == clientId) {
|
if (clientObject.clientId == clientId) {
|
||||||
log.DEBUG("Found client ID from given ID: ", clientObject);
|
log.DEBUG("Found client ID from given ID: ", clientObject);
|
||||||
return clientObject
|
return clientObject
|
||||||
@@ -139,3 +139,25 @@ exports.filterAutocompleteValues = async (interaction, options) => {
|
|||||||
filtered.map(option => ({ name: option, value: option })),
|
filtered.map(option => ({ name: option, value: option })),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter an array of nodeObjects to get all unique presets within
|
||||||
|
*
|
||||||
|
* @param {Array} nodeObjects An array of nodeObjects to get the presets from
|
||||||
|
* @returns {Array} Presets available from given nodeObjects
|
||||||
|
*/
|
||||||
|
exports.filterPresetsAvailable = async (nodeObjects) => {
|
||||||
|
log.DEBUG("Node objects: ", nodeObjects);
|
||||||
|
var presetsAvailable = [];
|
||||||
|
for (const nodeObject of nodeObjects) {
|
||||||
|
log.DEBUG("Node object: ", nodeObject);
|
||||||
|
presetsAvailable.push.apply(presetsAvailable, nodeObject.presets);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.DEBUG("All Presets available: ", presetsAvailable);
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
presetsAvailable = [...new Set(presetsAvailable)];
|
||||||
|
log.DEBUG("DeDuped Presets available: ", presetsAvailable);
|
||||||
|
return presetsAvailable;
|
||||||
|
}
|
||||||
@@ -1,133 +1,64 @@
|
|||||||
<%- include('partials/htmlHead.ejs', {'page': page}) %>
|
<%- include('partials/htmlHead.ejs', {'page': page}) %>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4 mt-2">
|
||||||
|
<%- include('partials/valueChip.ejs', {
|
||||||
|
'title': 'Nodes in the Network',
|
||||||
|
'bgColor': "orange-dark",
|
||||||
|
'value': nodes.length,
|
||||||
|
'progressPercent': false,
|
||||||
|
'icon': 'server'
|
||||||
|
}) %>
|
||||||
|
|
||||||
|
<%- include('partials/valueChip.ejs', {
|
||||||
|
'title': 'Nodes Online',
|
||||||
|
'bgColor': "green",
|
||||||
|
'value': nodes.filter(node => node.online).length,
|
||||||
|
'progressPercent': false,
|
||||||
|
'icon': 'cpu-fill'
|
||||||
|
}) %>
|
||||||
|
|
||||||
|
<%- include('partials/valueChip.ejs', {
|
||||||
|
'title': 'Nodes with Discord Connections',
|
||||||
|
'bgColor': "blue-dark",
|
||||||
|
'value': connections.length,
|
||||||
|
'progressPercent': false,
|
||||||
|
'icon': 'gear-wide-connected'
|
||||||
|
}) %>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-4">
|
||||||
|
<p><h3><b>Current Connections</b></h3></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4">
|
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4">
|
||||||
<div class="col-xl-3 col-lg-6">
|
<% for(const conn of connections) { %>
|
||||||
<div class="info-card l-bg-cherry">
|
<%- include('partials/connectionCard.ejs', {'connection': conn}) %>
|
||||||
<div class="card-statistic p-4">
|
<%}%>
|
||||||
<div class="card-icon card-icon-large"><i class="bi bi-cart"></i></div>
|
</div>
|
||||||
<div class="mb-4">
|
|
||||||
<h5 class="card-title mb-0">New Orders</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row align-items-center mb-2 d-flex">
|
|
||||||
<div class="col-8">
|
|
||||||
<h2 class="d-flex align-items-center mb-0">
|
|
||||||
3,243
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 text-right">
|
|
||||||
<span>12.5% <i class="fa fa-arrow-up"></i></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress mt-1 " data-height="8" style="height: 8px;">
|
|
||||||
<div class="progress-bar l-bg-cyan" role="progressbar" data-width="25%" aria-valuenow="25"
|
|
||||||
aria-valuemin="0" aria-valuemax="100" style="width: 25%;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xl-3 col-lg-6">
|
<div class="my-4">
|
||||||
<div class="info-card l-bg-blue-dark">
|
<p><h3><b>Online Nodes</b></h3></p>
|
||||||
<div class="card-statistic p-4">
|
</div>
|
||||||
<div class="card-icon card-icon-large"><i class="bi bi-cart"></i></div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<h5 class="card-title mb-0">New Orders</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row align-items-center mb-2 d-flex">
|
|
||||||
<div class="col-8">
|
|
||||||
<h2 class="d-flex align-items-center mb-0">
|
|
||||||
3,243
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 text-right">
|
|
||||||
<span>12.5% <i class="fa fa-arrow-up"></i></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress mt-1 " data-height="8" style="height: 8px;">
|
|
||||||
<div class="progress-bar l-bg-cyan" role="progressbar" data-width="25%" aria-valuenow="25"
|
|
||||||
aria-valuemin="0" aria-valuemax="100" style="width: 25%;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xl-3 col-sm-6">
|
|
||||||
<div class="card node-card">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="dropdown float-end">
|
|
||||||
<a class="text-muted dropdown-toggle font-size-16" href="#" role="button"
|
|
||||||
data-bs-toggle="dropdown" aria-haspopup="true"><i
|
|
||||||
class="bx bx-dots-horizontal-rounded"></i></a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end"><a class="dropdown-item" href="#">Edit</a><a
|
|
||||||
class="dropdown-item" href="#">Action</a><a class="dropdown-item"
|
|
||||||
href="#">Remove</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div><img src="https://bootdey.com/img/Content/avatar/avatar1.png" alt=""
|
|
||||||
class="avatar-md rounded-circle img-thumbnail" /></div>
|
|
||||||
<div class="flex-1 ms-3">
|
|
||||||
<h5 class="font-size-16 mb-1"><a href="#" class="text-dark">Phyllis Gatlin</a></h5>
|
|
||||||
<span class="badge badge-soft-success mb-0">Full Stack Developer</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 pt-1">
|
|
||||||
<p class="text-muted mb-0"><i
|
|
||||||
class="mdi mdi-phone font-size-15 align-middle pe-2 text-primary"></i> 070 2860 5375
|
|
||||||
</p>
|
|
||||||
<p class="text-muted mb-0 mt-2"><i
|
|
||||||
class="mdi mdi-email font-size-15 align-middle pe-2 text-primary"></i>
|
|
||||||
PhyllisGatlin@spy.com</p>
|
|
||||||
<p class="text-muted mb-0 mt-2"><i
|
|
||||||
class="mdi mdi-google-maps font-size-15 align-middle pe-2 text-primary"></i> 52
|
|
||||||
Ilchester MYBSTER 9WX</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xl-3 col-sm-6">
|
|
||||||
<div class="card node-card">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="dropdown float-end">
|
|
||||||
<a class="text-muted dropdown-toggle font-size-16" href="#" role="button"
|
|
||||||
data-bs-toggle="dropdown" aria-haspopup="true"><i
|
|
||||||
class="bx bx-dots-horizontal-rounded"></i></a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end"><a class="dropdown-item" href="#">Edit</a><a
|
|
||||||
class="dropdown-item" href="#">Action</a><a class="dropdown-item"
|
|
||||||
href="#">Remove</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div><img src="https://bootdey.com/img/Content/avatar/avatar5.png" alt=""
|
|
||||||
class="avatar-md rounded-circle img-thumbnail" /></div>
|
|
||||||
<div class="flex-1 ms-3">
|
|
||||||
<h5 class="font-size-16 mb-1"><a href="#" class="text-dark">Diana Owens</a></h5>
|
|
||||||
<span class="badge badge-soft-danger mb-0">UI/UX Designer</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 pt-1">
|
|
||||||
<p class="text-muted mb-0"><i
|
|
||||||
class="mdi mdi-phone font-size-15 align-middle pe-2 text-primary"></i> 087 6321 3235
|
|
||||||
</p>
|
|
||||||
<p class="text-muted mb-0 mt-2"><i
|
|
||||||
class="mdi mdi-email font-size-15 align-middle pe-2 text-primary"></i>
|
|
||||||
DianaOwens@spy.com</p>
|
|
||||||
<p class="text-muted mb-0 mt-2"><i
|
|
||||||
class="mdi mdi-google-maps font-size-15 align-middle pe-2 text-primary"></i> 52
|
|
||||||
Ilchester MYBSTER 9WX</p>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-2 pt-4">
|
|
||||||
<button type="button" class="btn btn-soft-primary btn-sm w-50"><i
|
|
||||||
class="bx bx-user me-1"></i> Profile</button>
|
|
||||||
<button type="button" class="btn btn-primary btn-sm w-50"><i
|
|
||||||
class="bx bx-message-square-dots me-1"></i> Contact</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4">
|
||||||
|
<% for(const node of nodes.filter(node => node.online)) { %>
|
||||||
|
<%- include('partials/nodeCard.ejs', {'node': node}) %>
|
||||||
|
<%}%>
|
||||||
|
</div>
|
||||||
|
<div class="my-4">
|
||||||
|
<p><h3><b>Offline Nodes</b></h3></p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4">
|
||||||
|
<% for(const node of nodes.filter(node => node.online == false)) { %>
|
||||||
|
<%- include('partials/nodeCard.ejs', {'node': node}) %>
|
||||||
|
<%}%>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%- include('partials/bodyEnd.ejs') %>
|
<%- include('partials/bodyEnd.ejs') %>
|
||||||
|
|
||||||
<%- include('partials/htmlFooter.ejs') %>
|
<%- include('partials/htmlFooter.ejs') %>
|
||||||
29
Server/views/partials/connectioncard.ejs
Normal file
29
Server/views/partials/connectioncard.ejs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<div class="col-xl-3 col-sm-6">
|
||||||
|
<div class="card node-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="dropdown float-end">
|
||||||
|
<a class="text-muted dropdown-toggle font-size-16" href="#" role="button" data-bs-toggle="dropdown"
|
||||||
|
aria-haspopup="true">
|
||||||
|
<i class="bx bx-dots-horizontal-rounded"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
|
<a class="dropdown-item node-action">Edit</a>
|
||||||
|
<a class="dropdown-item node-action" onclick="">Send Heartbeat</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-1 ms-3">
|
||||||
|
<h5 class="font-size-16 mb-1"><a class="text-dark">
|
||||||
|
<%= connection.clientObject.name%>
|
||||||
|
</a></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 pt-1">
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
<i class="bi bi-cpu-fill font-size-15 align-middle pe-2 text-primary"></i>
|
||||||
|
Node ID: <a href="/node/<%= connection.node.id %>"><%= connection.node.id %></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
32
Server/views/partials/valueChip.ejs
Normal file
32
Server/views/partials/valueChip.ejs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<div class="col-xl-3 col-lg-6">
|
||||||
|
<div class="info-card l-bg-<%=bgColor%>">
|
||||||
|
<div class="card-statistic p-4">
|
||||||
|
<div class="card-icon card-icon-large"><i class="bi bi-<%=icon%>"></i></div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<%=title%>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="row align-items-center mb-2 d-flex">
|
||||||
|
<div class="col-8">
|
||||||
|
<h2 class="d-flex align-items-center mb-0">
|
||||||
|
<%=value%>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<% if (progressPercent) {%>
|
||||||
|
<div class="col-4 text-right">
|
||||||
|
<span>
|
||||||
|
<%=progressPercent%>%<i class="fa fa-arrow-up"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<%}%>
|
||||||
|
</div>
|
||||||
|
<% if (progressPercent) {%>
|
||||||
|
<div class="progress mt-1 " data-height="8" style="height: 8px;">
|
||||||
|
<div class="progress-bar l-bg-cyan" role="progressbar" data-width="<%=progressPercent%>%" aria-valuenow="<%=progressPercent%>"
|
||||||
|
aria-valuemin="0" aria-valuemax="100" style="width: <%=progressPercent%>%;"></div>
|
||||||
|
</div>
|
||||||
|
<%}%>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user