diff --git a/htdocs/css/style.css b/htdocs/css/style.css
index c25a957..167ed65 100644
--- a/htdocs/css/style.css
+++ b/htdocs/css/style.css
@@ -749,6 +749,10 @@ td.table_label {
}
span.red2 {
+ background-color: #d9667a;
+ }
+
+ body.dark span.red2 {
background-color: #ac394d;
}
@@ -762,7 +766,7 @@ td.table_label {
}
.wflog.grid-item {
- border: 1px solid #ccc;
+ border: 0.1px solid rgba(48,48,48,1);
padding: 10px;
word-wrap: break-word;
overflow: hidden;
@@ -772,4 +776,198 @@ td.table_label {
.wflog.grid-title {
/* font-weight: bold; */
font-size: 1.6em;
- }
\ No newline at end of file
+ }
+
+ .flex-container {
+ display: flex;
+ white-space: nowrap;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ /* align-items: center; */
+ }
+
+ .flex-container-stats {
+ display: flex;
+ white-space: nowrap;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ /* align-items: center; */
+ }
+
+ .upcoming.grid-container {
+ display: grid;
+ grid-template-columns: repeat(6, minmax(240px, 1fr));
+ gap: 14px;
+ padding: 12px;
+ direction: ltr;
+ }
+
+ @media (max-width: 1600px) { /* Adjust the max-width value based on when you want the change to occur */
+ .upcoming.grid-container {
+ grid-template-columns: repeat(4, minmax(240px, 1fr)); /* Change to 6 columns when screen width is 768px or smaller */
+ }
+ }
+
+ @media (max-width: 1200px) { /* Adjust the max-width value based on when you want the change to occur */
+ .upcoming.grid-container {
+ grid-template-columns: repeat(3, minmax(240px, 1fr)); /* Change to 6 columns when screen width is 768px or smaller */
+ }
+ }
+
+ @media (max-width: 900px) { /* Adjust the max-width value based on when you want the change to occur */
+ .upcoming.grid-container {
+ grid-template-columns: repeat(2, minmax(240px, 1fr)); /* Change to 6 columns when screen width is 768px or smaller */
+ }
+ }
+
+ .stats.grid-container {
+ display: grid;
+ /* grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); */
+ grid-template-columns: repeat(10, 1fr);
+ align-items: center;
+ gap: 14px;
+ padding: 4px;
+ direction: ltr;
+ /* background-color: #80808005; */
+ border-radius: 12px;
+
+ }
+
+ @media (max-width: 1200px) { /* Adjust the max-width value based on when you want the change to occur */
+ .stats.grid-container {
+ grid-template-columns: repeat(5, 1fr); /* Change to 6 columns when screen width is 768px or smaller */
+ }
+ }
+
+ .upcoming.grid-item {
+ border: 1px solid #ccccccde;
+ /* transition: border-width 0.3s; */
+ padding: 8px;
+ border-radius: 12px;
+ word-wrap: break-word;
+ white-space: wrap;
+ overflow: hidden;
+ /* white-space: normal; */
+ direction: ltr;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ /* vertical-align: middle; */
+ }
+
+ .stats.grid-item {
+ border: 1px solid #ccccccde;
+ background-color: #FAFAFA;
+ /* transition: border-width 0.3s; */
+ padding: 4px;
+ border-radius: 12px;
+ word-wrap: break-word;
+ white-space: wrap;
+ overflow: hidden;
+ /* white-space: normal; */
+ direction: ltr;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ /* vertical-align: middle; */
+ }
+ body.dark .stats.grid-item {
+ background-color: initial;
+ }
+ body.dark .stats.grid-item:hover {
+ color: #FFF;
+ }
+
+
+ body.dark .upcoming.grid-item {
+ box-shadow: 0px 4px 8px 0px rgba(0,0,0,0.75);
+ }
+
+ body.dark .stats.grid-item {
+ box-shadow: 0px 4px 8px 0px rgba(0,0,0,0.75);
+ }
+
+ .upcoming.minute.grid-item {
+ background-color: #b7ffb770
+ }
+
+ body.dark .upcoming.minute.grid-item {
+ background-color: #2c702cDD;
+ transition: background-color 3s;
+ /* rgb(0,108,0); */
+ /* color: #FFF; */
+ color: #d3c7c7
+ }
+
+ .upcoming.grid-item a {
+ font-size: 1.1em;
+ }
+
+ body.dark .upcoming.grid-item a {
+ color: #3a639b;
+ }
+
+ body.dark .upcoming.minute.grid-item a {
+ color: #d3c7c7 !important;
+ font-size: 1.2em;
+ }
+
+ body.dark .upcoming.soon.grid-item a {
+ /* color: #301919!important;
+ */
+ /* color: #efd2d2 !important; */
+ /* color: #d3c7c7; */
+ color: #d3c7c7 !important;
+ font-size: 1.2em;
+ }
+
+ .upcoming.minute.grid-item a {
+ color: rgb(84, 84, 84) !important;
+ font-size: 1.2em;
+ }
+
+ .upcoming.soon.grid-item a {
+ color: rgb(84, 84, 84) !important;
+ font-size: 1.2em;
+ }
+
+ /* body.dark upcoming.minute.grid-item a {
+ color: #FFF !important;
+ } */
+
+ .upcoming.soon.grid-item {
+ background-color: #f5e05870;
+
+ /* border-color: white; */
+ }
+
+ body.dark .upcoming.soon.grid-item {
+ background-color: rgba(255, 115, 0, 0.7);
+ /* color: #301919!important; */
+ /* color: #fff; */
+ color: #d3c7c7;
+ /* color: rgb(84, 84, 84); */
+ }
+
+ .upcoming.grid-item:hover {
+ border-color: rgba(46, 38, 12, 0.4);
+ /* background-color:rgb(0, 255, 0) */
+
+ }
+
+ body.dark .upcoming.grid-item:hover {
+ /* border-color: rgba(46, 38, 12, 0.4); */
+ color: #FFF;
+ }
+
+
+ body.dark .upcoming.grid-item {
+ border: 1px solid #cccccc26;
+ }
+
+ body.dark .stats.grid-item {
+ border: 1px solid #cccccc26;
+ }
+
+ .upcoming.grid-title {
+ /* font-weight: bold; */
+ font-size: 1.2em;
+ }
+
+
diff --git a/htdocs/js/pages/Home.class.js b/htdocs/js/pages/Home.class.js
index e1cc214..387a0ac 100644
--- a/htdocs/js/pages/Home.class.js
+++ b/htdocs/js/pages/Home.class.js
@@ -8,12 +8,14 @@ Class.subclass( Page.Base, "Page.Home", {
this.worker.onmessage = this.render_upcoming_events.bind(this);
this.div.html(`
+
-
+
+
@@ -46,6 +48,7 @@ Class.subclass( Page.Base, "Page.Home", {
+
@@ -57,7 +60,6 @@ Class.subclass( Page.Base, "Page.Home", {
-
Event Queues
@@ -72,6 +74,12 @@ Class.subclass( Page.Base, "Page.Home", {
+
+
+
+
+
+
`)
},
@@ -110,9 +118,16 @@ Class.subclass( Page.Base, "Page.Home", {
var html = '';
html += 'Upcoming Events';
- html += '
';
+ html += '
';
- html += `
`;
+ let ue_options = [
+ {title: "Grid View", id: "grid"},
+ {title: "Compact View", id: "compact"},
+ {title: "Show All", id: "all"}
+ ]
+ let up_event_select = render_menu_options( ue_options , app.getPref('fe_up_eventlimit') || 'grid', false ) + ''
+
+ html += `
`;
html += '
';
html += '
';
html += '
';
@@ -176,28 +191,51 @@ Class.subclass( Page.Base, "Page.Home", {
let errBg = stats.jobs_completed > 0 && (stats.jobs_failed || 0)/stats.jobs_completed > (parseFloat(ui.err_rate) || 0.03) ? 'red2' : 'gray'
let errTitle = Object.entries(stats.errorLog || {}).slice(0,20).sort((a,b)=> a[1] < b[1] ? 1 : -1).map(e=>`${e[0]}:\t
${e[1]}`).join("\n")
-
- html += `
-
- `
-
- // $('#d_home_header_stats').html(html);
- document.getElementById('d_home_header_stats').innerHTML = html;
+ // xhtml
+
+ let failed_badge = `
${stats.jobs_failed || 0} `
+
+ status_bar = [
+ {name: "EVENTS", value: active_events.length},
+ {name: "CATS", value: app.categories.length},
+ // {name: "PLUGINS", value: app.plugins.length},
+ {name: "JOBS", value: stats.jobs_completed || 0},
+ {name: "FAILED", value: failed_badge},
+ {name: "SUCCESS", value: pct( (stats.jobs_completed || 0) - (stats.jobs_failed || 0), stats.jobs_completed || 1 )},
+ {name: "LOG SIZE", value: '2K'},
+ {name: "UPTIME", value: get_text_from_seconds( mserver.uptime || 0, false, true )},
+ {name: "CPU", value: `${short_float(total_cpu)}%`},
+ {name: "MEMORY", value: get_text_from_bytes(total_mem)},
+ {name: "SERVERS", value: num_keys(servers)}
+ ]
+
+ html = '
'
+ status_bar.forEach(e=>{
+ html += `
+
${e.name}:
+
${e.value}
+
+ `
+ })
+
+ html += "
"
+
+ html2 = `
+
+
EVENTS:
${ active_events.length}
+
CATEGORIES: ${app.categories.length}
+
PLUGINS: ${app.plugins.length}
+
COMPLETED: ${stats.jobs_completed || 0 }
+
+
SUCCESS: ${pct( (stats.jobs_completed || 0) - (stats.jobs_failed || 0), stats.jobs_completed || 1 ) }
+
+
UPTIME: ${get_text_from_seconds( mserver.uptime || 0, false, true )}
+
CPU: ${short_float(total_cpu)}%
+
MEMORY: ${get_text_from_bytes(total_mem)}
+
SERVERS: ${num_keys(servers)}
+
+ `
+ document.getElementById('d_home_header_stats').innerHTML = html;
},
refresh_upcoming_events: function() {
@@ -214,6 +252,7 @@ Class.subclass( Page.Base, "Page.Home", {
nav_upcoming: function(offset) {
// refresh upcoming events with new offset
+ app.setPref('fe_up_eventlimit', document.getElementById('fe_up_eventlimit').value)
this.upcoming_offset = offset;
this.render_upcoming_events({
data: this.upcoming_events
@@ -259,7 +298,7 @@ Class.subclass( Page.Base, "Page.Home", {
var stub = e.data[idx];
var item = find_object( app.schedule, { id: stub.id } ) || {};
- if (viewType == "Compact View") { // one row per event, use badge for job count
+ if (viewType == "compact" || viewType == "grid") { // one row per event, use badge for job count
var currSched = moment.tz(stub.epoch * 1000, item.timezone || app.tz).format("h:mm A z");
var currCD = get_text_from_seconds_round(Math.max(60, stub.epoch - now), false);
@@ -291,12 +330,14 @@ Class.subclass( Page.Base, "Page.Home", {
events.push( stub );
} // foreach item in schedule
+
+ let xhtml = ''
var size = get_inner_window_size();
var col_width = Math.floor( ((size.width * 0.9) + 50) / 7 );
var cols = ['Event Name', 'Category', 'Plugin', 'Target', 'Scheduled Time', 'Countdown', 'Actions'];
- var limit = 25;
+ var limit = 24;
html += this.getPaginatedTable({
resp: {
@@ -305,7 +346,7 @@ Class.subclass( Page.Base, "Page.Home", {
length: events.length
}
},
- cols: cols,
+ cols: viewType === 'grid' ? [] : cols,
data_type: 'pending event',
limit: limit,
offset: this.upcoming_offset,
@@ -335,7 +376,7 @@ Class.subclass( Page.Base, "Page.Home", {
}
var badge = '';
- if(viewType == "Compact View") {
+ if(viewType == "compact" || viewType == "grid") {
var overLimitRows = stubCounter[stub.id] > maxSchedRows ? ` + ${stubCounter[stub.id] - maxSchedRows} more` : '';
var scheduleList = stubTitle[stub.id] + `
${overLimitRows}`
var jobCount = stubCounter[stub.id]
@@ -344,9 +385,15 @@ Class.subclass( Page.Base, "Page.Home", {
}
var eventName = self.getNiceEvent('
' + item.title + '', col_width, 'float:left', '
')
+
+ if( viewType == "grid") {
+ badge = stubCounter[stub.id] > 1 ? `+${stubCounter[stub.id]-1}` : ''
+ nice_countdown = `
${nice_countdown}
`
+ // badge = `
${badge}
`
+ }
var tds = [
- `
${eventName} ${badge}
`,
+ `
${eventName}${badge}`,
self.getNiceCategory( cat, col_width ),
self.getNicePlugin( plugin, col_width ),
self.getNiceGroup( group, item.target, col_width ),
@@ -362,11 +409,36 @@ Class.subclass( Page.Base, "Page.Home", {
}
if(!app.state.enabled) tds.className += ' disabled'
+
+ if( viewType == "grid") { // tile/grid view
+
+ let proximity = ''
+ if(stub.epoch*1000 - Date.now() < 1760000) proximity = 'soon'
+ if(stub.epoch*1000 - Date.now() < 360000) proximity = 'minute'
+
+ xhtml += `
+
+
220 ? 'style="justify-content: space-around;"' : ''}>
+
${tds[0]}
+
${nice_countdown}
+
- return tds;
+
+ `
+ //
+
+ }
+ else { // compact table
+ return tds
+ }
+
+
+
} // row callback
}); // table
-
+
+ $('#upcoming_grid').html(xhtml)
+
// $('#d_home_upcoming_events').removeClass('loading').html( html );
let upcoming = document.getElementById('d_home_upcoming_events');
upcoming.classList.remove('loading');
@@ -406,19 +478,27 @@ Class.subclass( Page.Base, "Page.Home", {
}
let isDark = app.getPref('theme') === 'dark'
- let green = isDark ? '#44bb44' : 'lightgreen' // success
- let orange = isDark ? '#bbbb44' : 'orange' // warning
- let red = isDark ? '#bb4444' : 'pink' // error
+ let green = isDark ? '#44bb44DD' : '#90EE90AA' // success
+ let orange = isDark ? '#bbbb44DD' : '#FFA500AA' // warning
+ let red = isDark ? '#bb4444DD' : '#F88379AA' // error
let statusMap = { 0: green, 255: orange }
let labels = jobs.map(e => '')
- if(jobLimit < 100) labels = jobs.map((j, i) => i == 0 ? j.event_title.substring(0, 4) : j.event_title);
+ if(jobLimit <= 100) labels = jobs.map((j, i) => i == 0 ? j.event_title.substring(0, 4) : j.event_title);
+
+ let ctx = document.getElementById('d_home_completed_jobs');
+ // var gradient = ctx.createLinearGradient(0, 0, 0, 400);
+ // gradient.addColorStop(0, 'rgba(250,174,50,1)');
+ // gradient.addColorStop(1, 'rgba(250,174,50,0)');
+
+
let datasets = [{
label: 'Completed Jobs',
// data: jobs.map(j => Math.ceil(j.elapsed/60)),
data: jobs.map(j => Math.ceil(j.elapsed) + 1),
backgroundColor: jobs.map(j => statusMap[j.code] || red),
- jobs: jobs
+ jobs: jobs,
+
// borderWidth: 0.3
}];
let scaleType = document.getElementById('fe_cmp_job_chart_scale').value || 'logarithmic';
@@ -434,8 +514,7 @@ Class.subclass( Page.Base, "Page.Home", {
return
}
- let ctx = document.getElementById('d_home_completed_jobs');
-
+
app.jobHistoryChart = new Chart(ctx, {
type: 'bar',
data: {
@@ -446,8 +525,10 @@ Class.subclass( Page.Base, "Page.Home", {
options: {
legend: { display: false },
+ animation: {duration: 0},
layout: { padding: { bottom: jobLimit > 50 ? 50 : 20 } },
tooltips: {
+
yAlign: 'top',
titleFontSize: 14,
titleFontColor: 'orange',
@@ -474,7 +555,7 @@ Class.subclass( Page.Base, "Page.Home", {
}],
yAxes: [{
type: scaleType,
- gridLines: { color: 'rgb(170, 170, 170)', lineWidth: 0.3 },
+ gridLines: { color: 'rgb(170, 170, 170)', lineWidth: 0.3 },
ticks: {
display: false,
beginAtZero: true,
@@ -516,10 +597,11 @@ Class.subclass( Page.Base, "Page.Home", {
return (a.time_start < b.time_start) ? 1 : -1;
} );
- var cols = ['Job ID', 'Event Name', 'Argument', 'Category', 'Hostname', 'Elapsed', 'Progress', 'Remaining', 'Performance', 'Memo', 'Actions'];
+ var cols = this.jobs.length > 0 ? ['Job ID', 'Event Name', 'Argument', 'Category', 'Hostname', 'Elapsed', 'Progress', 'Remaining', 'Performance', 'Memo', 'Actions'] : [];
// render table
var self = this;
+
html += this.getBasicTable( this.jobs, cols, 'active job', function(job, idx) {
var actions = [
// '
Details',