From ae875011761af2b68ace481a5f8e50bfb0ddf3d3 Mon Sep 17 00:00:00 2001 From: miketwc1984 Date: Tue, 9 Apr 2024 22:23:50 -0400 Subject: [PATCH] home page redesign --- htdocs/css/style.css | 202 +++++++++++++++++++++++++++++++++- htdocs/js/pages/Home.class.js | 166 +++++++++++++++++++++------- 2 files changed, 324 insertions(+), 44 deletions(-) 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", {
- +
+
+ + + + `) }, @@ -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 += `
 ' + this.render_target_menu_options( args.target ) + '
'; 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 += ` -
Server Stats -
EVENTS:  ${ active_events.length} 
-
CATEGORIES:  ${app.categories.length} 
-
PLUGINS:  ${app.plugins.length} 
-
JOBS COMPLETED TODAY:  ${stats.jobs_completed || 0 } 
-
FAILED:  - ${stats.jobs_failed || 0}  -
-
SUCCESS RATE:  ${pct( (stats.jobs_completed || 0) - (stats.jobs_failed || 0), stats.jobs_completed || 1 ) } 
-
AVG LOG SIZE:  2K 
- -
MANAGER 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)} 
-
- ` - - // $('#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 } 
+
FAILED: ${failed_badge}
+
SUCCESS:  ${pct( (stats.jobs_completed || 0) - (stats.jobs_failed || 0), stats.jobs_completed || 1 ) } 
+
LOG SIZE:  2K 
+
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',