From 792053065c1191c2703b43c2f2ce4a337edf6a9b Mon Sep 17 00:00:00 2001 From: miketwc1984 Date: Fri, 19 Apr 2024 14:38:42 -0400 Subject: [PATCH] v190 main commit --- htdocs/css/style.css | 265 +++++++++++++++++-------- htdocs/js/app.js | 12 ++ htdocs/js/pages/Base.class.js | 19 +- htdocs/js/pages/History.class.js | 96 ++++----- htdocs/js/pages/Home.class.js | 90 ++++----- htdocs/js/pages/JobDetails.class.js | 195 ++++++------------ htdocs/js/pages/Schedule.class.js | 293 ++++++++++++++++++++-------- 7 files changed, 565 insertions(+), 405 deletions(-) diff --git a/htdocs/css/style.css b/htdocs/css/style.css index 167ed65..9206f11 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -650,7 +650,6 @@ td.table_label { tr.focus td { animation: 4s focus; - } body.dark span.color_label.gray { @@ -781,11 +780,16 @@ td.table_label { .flex-container { display: flex; white-space: nowrap; - flex-wrap: wrap; + flex-wrap: nowrap; justify-content: space-between; /* align-items: center; */ } + .flex-container.widget { + align-items: center; + justify-content:space-between; + } + .flex-container-stats { display: flex; white-space: nowrap; @@ -796,34 +800,23 @@ td.table_label { .upcoming.grid-container { display: grid; - grid-template-columns: repeat(6, minmax(240px, 1fr)); - gap: 14px; - padding: 12px; + grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); + gap: 12px; + padding: 10px; 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 */ - } + .upcoming.schedule.grid-container { + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + padding-top: 0px; + gap: 14px; + } .stats.grid-container { display: grid; /* grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); */ - grid-template-columns: repeat(10, 1fr); + grid-template-columns: repeat(10, auto); align-items: center; gap: 14px; padding: 4px; @@ -833,47 +826,135 @@ td.table_label { } - @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 { + .stats.grid-item { border: 1px solid #ccccccde; + background-color: var(--box-background-color); + /* background-color: #FAFAFA; */ /* transition: border-width 0.3s; */ - padding: 8px; + padding: 4px; 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); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* vertical-align: middle; */ } - .stats.grid-item { + body.dark .stats.grid-item { + background-color: initial; + } + body.dark .stats.grid-item:hover { + color: #FFF; + background-color: initial; + } + .stats.grid-item:hover { + background-color: rgba(128,128,128, 0.1); + } + + @media (max-width: 1200px) { + .stats.grid-container { + grid-template-columns: repeat(5, 1fr); + } + } + + .job-details.grid-container { + display: grid; + /* grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); */ + grid-template-columns: repeat(7, auto); + align-items: center; + gap: 6px; + padding: 0px; + padding-bottom: 12px; + margin-top: 12px; + /* background-color: var(--box-background-color); */ + background-color: initial; + border: 0px solid var( --border-color); + border-radius: 6px; + } + + .job-details.running.grid-container { + grid-template-columns: repeat(6, auto); + } + + body.dark .job-details.grid-container { + background-color: var(--background-color); + } + + .job-details.grid-item { + display: flex; + align-items: baseline; + justify-content: space-evenly; + border: 1px solid var( --border-color); + background-color: var(--box-background-color); + flex-wrap: wrap; + padding-top: 8px; + border-radius: 6px; + } + + body.dark .job-details.grid-item:hover div.info_value { + color: white; + text-shadow: none; + } + + .upcoming.grid-item { border: 1px solid #ccccccde; - background-color: #FAFAFA; /* transition: border-width 0.3s; */ - padding: 4px; + padding: 8px; 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); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* vertical-align: middle; */ } - body.dark .stats.grid-item { - background-color: initial; + + .upcoming.grid-item.focus { + animation: 4s focus; } - body.dark .stats.grid-item:hover { - color: #FFF; + + + body.dark .upcoming.schedule.grid-item { + background-color: #44444444 + /* color: #fff */ + } + + .upcoming.schedule.grid-item { + background-color: #fafafa + /* color: #fff */ + } + + .event-error { + color: #bb4444cc; + } + .event-success { + color: #44bb4460; + } + .event-warning { + color: #dbdb34bb; } + .event-none { + color: #44444400; + } + + .section-divider { + grid-column: 1 / -1; + text-align: left; + /* color: initial; */ + font-size: 1.4em; + font-weight: bold; + } + .upcoming.warning.grid-item { + background-color: #f5e05870;; + } + + .upcoming.error.grid-item { + background-color: pink; + } body.dark .upcoming.grid-item { box-shadow: 0px 4px 8px 0px rgba(0,0,0,0.75); @@ -887,75 +968,82 @@ td.table_label { background-color: #b7ffb770 } - body.dark .upcoming.minute.grid-item { - background-color: #2c702cDD; - transition: background-color 3s; - /* rgb(0,108,0); */ - /* color: #FFF; */ - color: #d3c7c7 - } + body.dark .upcoming.minute.grid-item { + background-color: #2c702cDD; + transition: background-color 3s; + color: #d3c7c7 + } - .upcoming.grid-item a { - font-size: 1.1em; - } + .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.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; - } + body.dark .upcoming.soon.grid-item a { + 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; - } + .upcoming.minute.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 a { + color: rgb(84, 84, 84) !important; + font-size: 1.2em; + } .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) */ - + /* background-color:rgb(0, 255, 0) */ } - body.dark .upcoming.grid-item:hover { + body.dark .upcoming.grid-item:hover:not(.schedule) { /* border-color: rgba(46, 38, 12, 0.4); */ color: #FFF; } + .event-action { + font-weight: bold; + color:#817c7c; + text-decoration: none; + } + + .event-action:hover { + text-decoration: underline; + } + + .search { + width : 100px; + background-color:gray; + -webkit-transition: all 0.3s linear; + -moz-transition: all 0.3s linear; + border-radius : 15px; + outline:none; + } + + body.dark .event-action:hover { + font-weight: bold; + color:#555; + } body.dark .upcoming.grid-item { border: 1px solid #cccccc26; @@ -970,4 +1058,11 @@ td.table_label { font-size: 1.2em; } + .event-search { + padding-left:30px; + border:0.3px solid #ccccccaa; + border-radius:8px; + font-size: 1.2em; + } + diff --git a/htdocs/js/app.js b/htdocs/js/app.js index 1c9f46b..4ba5340 100644 --- a/htdocs/js/app.js +++ b/htdocs/js/app.js @@ -819,6 +819,18 @@ function get_pretty_int_list(arr, ranges) { return arr.slice(0, arr.length - 1).join(', ') + ' and ' + arr[ arr.length - 1 ]; } +function summarize_event_timing_short(timing) { + if(!timing) return "On Demand" + let type = 'Hourly' + let total = (timing.minutes || []).length || 60 + if(timing.hours) { total = total*(timing.hours.length || 24); type = 'Daily'} else { total = total*24} + if(timing.weekdays) { total = total*(timing.weekdays.length || 1); type = 'Weekly'} + if(timing.days) { total = total*(timing.days.length || 1); type = 'Monthly'} + if(timing.months) { total = total*(timing.months.length || 1); type = 'Yearly'} + if(timing.years) { total = total*(timing.years.length || 1); type = 'Custom'} + return `${type} :: ${total}` +} + function summarize_event_timing(timing, timezone, extra) { // summarize event timing into human-readable string if (!timing && extra) { diff --git a/htdocs/js/pages/Base.class.js b/htdocs/js/pages/Base.class.js index 192f69a..602b619 100644 --- a/htdocs/js/pages/Base.class.js +++ b/htdocs/js/pages/Base.class.js @@ -60,18 +60,31 @@ Class.subclass(Page, "Page.Base", { return '
 ' + id + '
'; }, - getNiceEvent: function (title, width, style, extra) { + getNiceEvent: function (title, width, style, extra, extraTooltip) { if (!width) width = 500; if (!title) return '(None)'; if (!style) style = ''; if (!extra) extra = ''; + let tooltip = title.notes ? title.notes.replace(/\"/g, """) : "" + if(extraTooltip) { + let cat = title.category_title || '(none)' + let plug = title.plugin_title || '(none)' + let target = title.group_title || '(none)' + tooltip = `Category: ${cat}
Plugin: ${plug}
Target: ${target}
Notes:

${tooltip}` + } + + let cat = title.category_title || '' + let plug = title.plugin_title || '' let icon_class = 'fa fa-clock-o'; if(title.plugin == 'workflow') icon_class = 'fa fa-folder'; - let notes = title.notes ? title.notes.replace(/\"/g, """) : "" + if (typeof (title) == 'object') { title = title.title } - return `
 ${title}${extra}
`; + + let icon = ` ` + + return `
${icon} ${title}${extra}
`; }, getNiceCategory: function (cat, width, collapse) { diff --git a/htdocs/js/pages/History.class.js b/htdocs/js/pages/History.class.js index 4cb5bc7..3d5fac7 100644 --- a/htdocs/js/pages/History.class.js +++ b/htdocs/js/pages/History.class.js @@ -300,6 +300,13 @@ Class.subclass( Page.Base, "Page.History", { if (!args.limit) args.limit = 50; app.api.post( 'app/get_event_history', copy_object(args), this.receive_event_stats.bind(this) ); }, + + togglePerfLegend: function() { + let chart = this.charts.perf + if(!chart) return; + chart.options.legend.display = !chart.options.legend.display + chart.update() + }, receive_event_stats: function(resp) { // render event stats page @@ -335,32 +342,11 @@ Class.subclass( Page.Base, "Page.History", { ['error_history', "Query History"], ] ); - html += '
'; + // html += '
'; + + let eventTitle = `${this.getNiceEvent(event.title, col_width)}` - html += '
Event Stats'; - - html += '
'; - html += '
EVENT NAME
'; - html += ''; - - html += '
CATEGORY NAME
'; - html += '
' + this.getNiceCategory(cat, col_width) + '
'; - - html += '
EVENT TIMING
'; - html += '
' + (event.enabled ? summarize_event_timing(event.timing, event.timezone) : '(Disabled)') + '
'; - html += '
'; - - html += '
'; - html += '
USERNAME
'; - html += '
' + this.getNiceUsername(event, false, col_width) + '
'; - - html += '
PLUGIN NAME
'; - html += '
' + this.getNicePlugin(plugin, col_width) + '
'; - - html += '
EVENT TARGET
'; - html += '
' + this.getNiceGroup(group, event.target, col_width) + '
'; - html += '
'; - + var total_elapsed = 0; var total_cpu = 0; var total_mem = 0; @@ -391,35 +377,29 @@ Class.subclass( Page.Base, "Page.History", { } } - html += '
'; - html += '
AVG. ELAPSED
'; - html += '
' + get_text_from_seconds(total_elapsed / count, true, false) + '
'; - - html += '
AVG. CPU
'; - html += '
' + short_float(total_cpu / count) + '%
'; - - html += '
AVG. MEMORY
'; - html += '
' + get_text_from_bytes( total_mem / count ) + '
'; - html += '
'; - - html += '
'; - html += '
SUCCESS RATE
'; - html += '
' + pct(total_success, count) + '
'; - - html += '
LAST RESULT
'; - html += '
' + nice_last_result + '
'; - - html += '
AVG. LOG SIZE
'; - html += '
' + get_text_from_bytes( total_log_size / count ) + '
'; - html += '
'; - - html += '
'; - html += '
'; + html += ` +
+
EVENT NAME:
${eventTitle}
+
CATEGORY:
${this.getNiceCategory(cat, col_width) }
+
PLUGIN:
${this.getNicePlugin(plugin, col_width)}
+
EVENT TARGET:
${this.getNiceGroup(group, event.target, col_width)}
+
USERNAME:
${this.getNiceUsername(event, false, col_width)}
+
EVENT TIMING:
${event.enabled ? summarize_event_timing(event.timing, event.timezone) : '(Disabled)'}
+ +
AVG CPU:
${short_float(total_cpu / count)}%
+
AVG MEMORY:
${get_text_from_bytes( total_mem / count )}
+
AVG LOG SIZE:
${get_text_from_bytes( total_log_size / count )}
+
AVG ELAPSED:
${get_text_from_seconds(total_elapsed / count, true, false)}
+
SUCCESS RATE:
${pct(total_success, count)}
+
LAST RESULT:
${nice_last_result}
+
+
+ ` // graph containers html += '
'; - html += '
Performance History
'; - html += '
'; + html += '
Performance History
'; + html += `
`; // $P().togglePerfLegend()` html += '
'; html += '
'; @@ -550,11 +530,14 @@ Class.subclass( Page.Base, "Page.History", { labels: { fontStyle: 'bold', padding: 15 - } + }, + }, + + title:{ display: false, - text: "" + text: "toggle legend" }, scales: { xAxes: [{ @@ -576,13 +559,16 @@ Class.subclass( Page.Base, "Page.History", { callback: function(value, index, values) { if (value < 0) return ''; return '' + get_text_from_seconds_round_custom(value, true); - } + }, + onClick: function(e,i) {$P().togglePerfLegend()} }, scaleLabel: { display: true, + onClick: $P().togglePerfLegend // labelString: 'value' } - }] + }], + }, tooltips: { mode: 'nearest', diff --git a/htdocs/js/pages/Home.class.js b/htdocs/js/pages/Home.class.js index 387a0ac..13be5b1 100644 --- a/htdocs/js/pages/Home.class.js +++ b/htdocs/js/pages/Home.class.js @@ -16,8 +16,6 @@ Class.subclass( Page.Base, "Page.Home", {
- -
@@ -48,7 +46,6 @@ Class.subclass( Page.Base, "Page.Home", {
-
@@ -74,12 +71,7 @@ Class.subclass( Page.Base, "Page.Home", {
-
-
- - - - +
`) }, @@ -194,7 +186,7 @@ Class.subclass( Page.Base, "Page.Home", { // xhtml let failed_badge = `${stats.jobs_failed || 0} ` - + status_bar = [ {name: "EVENTS", value: active_events.length}, {name: "CATS", value: app.categories.length}, @@ -202,7 +194,7 @@ Class.subclass( Page.Base, "Page.Home", { {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: "LOG SIZE", value: get_text_from_bytes((stats.jobs_log_size || 0) / (stats.jobs_completed || 1))}, {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)}, @@ -212,29 +204,14 @@ Class.subclass( Page.Base, "Page.Home", { html = '
' status_bar.forEach(e=>{ html += `
-
${e.name}: 
-
 ${e.value} 
+
${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; }, @@ -287,6 +264,7 @@ Class.subclass( Page.Base, "Page.Home", { this.upcoming_events = e.data; var viewType = document.getElementById('fe_up_eventlimit').value; + let isGrid = viewType === 'grid' // apply filters var events = []; @@ -298,7 +276,7 @@ Class.subclass( Page.Base, "Page.Home", { var stub = e.data[idx]; var item = find_object( app.schedule, { id: stub.id } ) || {}; - if (viewType == "compact" || viewType == "grid") { // one row per event, use badge for job count + if (viewType == "compact" || isGrid) { // 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); @@ -337,7 +315,7 @@ Class.subclass( Page.Base, "Page.Home", { var col_width = Math.floor( ((size.width * 0.9) + 50) / 7 ); var cols = ['Event Name', 'Category', 'Plugin', 'Target', 'Scheduled Time', 'Countdown', 'Actions']; - var limit = 24; + var limit = Math.round(window.innerWidth/350)*5 // try to fit 5 rows html += this.getPaginatedTable({ resp: { @@ -346,7 +324,7 @@ Class.subclass( Page.Base, "Page.Home", { length: events.length } }, - cols: viewType === 'grid' ? [] : cols, + cols: isGrid ? [] : cols, data_type: 'pending event', limit: limit, offset: this.upcoming_offset, @@ -368,6 +346,7 @@ Class.subclass( Page.Base, "Page.Home", { var nice_countdown = 'Now'; if (stub.epoch > now) { nice_countdown = get_text_from_seconds_round( Math.max(60, stub.epoch - now), false ); + nice_countdown_short = get_text_from_seconds_round( Math.max(60, stub.epoch - now), true ).replace(' hr', 'h'); } if (group && item.multiplex) { @@ -375,21 +354,29 @@ Class.subclass( Page.Base, "Page.Home", { group.multiplex = 1; } - var badge = ''; - if(viewType == "compact" || viewType == "grid") { + let badge = ''; + if(viewType == "compact" || isGrid) { var overLimitRows = stubCounter[stub.id] > maxSchedRows ? ` + ${stubCounter[stub.id] - maxSchedRows} more` : ''; var scheduleList = stubTitle[stub.id] + `${overLimitRows}` var jobCount = stubCounter[stub.id] - if(jobCount < 10) jobCount = ` ${jobCount} `; - var badge = `${jobCount}`; + if(jobCount < 10 ) jobCount = ` ${jobCount} `; + badge = `${jobCount}`; } - var eventName = self.getNiceEvent('' + item.title + '', col_width, 'float:left', '  ') + let extraSpace = isGrid ? ' ' : '  ' + let eventName = self.getNiceEvent('' + item.title + '', col_width, 'float:left', extraSpace) - if( viewType == "grid") { + if(isGrid) { + badge = stubCounter[stub.id] > 1 ? `+${stubCounter[stub.id]-1}` : '' - nice_countdown = `
${nice_countdown}
` - // badge = `
${badge}
` + + // turn minute/hour to min/h if event name is too long + if(Math.max(60, stub.epoch - now) <= 60*5) { // for minute/soon (larger font) + nice_countdown = `
${item.title.length > 18 ? nice_countdown_short : nice_countdown}
` + } + else { // regular + nice_countdown = `
${item.title.length > 26 ? nice_countdown_short : nice_countdown}
` + } } var tds = [ @@ -410,22 +397,20 @@ 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' + if (isGrid) { + let proximity = '' + if (stub.epoch - now <= 60*5) proximity = 'soon' + if (stub.epoch - now <= 60) proximity = 'minute' - xhtml += ` + xhtml += `
-
220 ? 'style="justify-content: space-around;"' : ''}> +
${tds[0]}
${nice_countdown}
-
- +
` - //
+ //
} else { // compact table @@ -591,10 +576,15 @@ Class.subclass( Page.Base, "Page.Home", { for (var id in app.activeJobs) { jobs.push( app.activeJobs[id] ); } + + if(jobs.length === 0) return '
No active jobs found
' // sort events by time_start descending this.jobs = jobs.sort( function(a, b) { - return (a.time_start < b.time_start) ? 1 : -1; + if(a.plugin === 'workflow') return -1 + if(b.plugin === 'workflow') return 1 + return (a.time_start - b.time_start) + // return (a.time_start > b.time_start) ? 1 : -1; } ); var cols = this.jobs.length > 0 ? ['Job ID', 'Event Name', 'Argument', 'Category', 'Hostname', 'Elapsed', 'Progress', 'Remaining', 'Performance', 'Memo', 'Actions'] : []; diff --git a/htdocs/js/pages/JobDetails.class.js b/htdocs/js/pages/JobDetails.class.js index b8444fa..a370d60 100644 --- a/htdocs/js/pages/JobDetails.class.js +++ b/htdocs/js/pages/JobDetails.class.js @@ -222,84 +222,55 @@ Class.subclass(Page.Base, "Page.JobDetails", { html += this.get_job_result_banner(job).replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, ""); // fieldset header - html += '
Job Details'; + html += '
Job Details'; - // if (event.id && !event.multiplex) html += '
Run Again
'; + let eventTitle = '(None)' + if (event.id) eventTitle = '' + this.getNiceEvent(job.event_title, col_width) + ''; + else if (job.event_title) eventTitle = this.getNiceEvent(job.event_title, col_width); - html += '
'; - html += '
JOB ID
'; - html += '
' + job.id + '
'; + let jobCategory = '(None)' + if (cat) jobCategory = this.getNiceCategory(cat, col_width); + else if (job.category_title) jobCategory= this.getNiceCategory({ title: job.category_title }, col_width); - html += '
EVENT NAME
'; - html += '
'; - if (event.id) html += '' + this.getNiceEvent(job.event_title, col_width) + ''; - else if (job.event_title) html += this.getNiceEvent(job.event_title, col_width); - else html += '(None)'; - html += '
'; - - // html += '
EVENT TIMING
'; - // html += '
' + (event.enabled ? summarize_event_timing(event.timing, event.timezone) : '(Disabled)') + '
'; - // html += '
'; - - html += '
ARGUMENT
'; // hist - html += '
' + encode_entities(job.arg || '(no argument)') + '
'; - html += '
'; - - html += '
'; - html += '
CATEGORY NAME
'; - html += '
'; - if (cat) html += this.getNiceCategory(cat, col_width); - else if (job.category_title) html += this.getNiceCategory({ title: job.category_title }, col_width); - else html += '(None)'; - html += '
'; - - html += '
PLUGIN NAME
'; - html += '
'; - if (plugin) html += this.getNicePlugin(plugin, col_width); - else if (job.plugin_title) html += this.getNicePlugin({ title: job.plugin_title }, col_width); - else html += '(None)'; - html += '
'; - - html += '
EVENT TARGET
'; - html += '
'; - if (group || event.target) html += this.getNiceGroup(group, event.target, col_width); - else if (job.nice_target) html += '
' + job.nice_target + '
'; - else html += '(None)'; - html += '
'; - html += '
'; - - html += '
'; - html += '
JOB SOURCE
'; - html += `
` + (job.source || 'Scheduler') + '
'; + let jobPlugin = '(None)' + if (plugin) jobPlugin = this.getNicePlugin(plugin, col_width); + else if (job.plugin_title) jobPlugin = this.getNicePlugin({ title: job.plugin_title }, col_width); - html += '
SERVER HOSTNAME
'; - html += '
' + this.getNiceGroup(null, job.hostname, col_width) + '
'; + let jobTarget = '(None)' + if (group || event.target) jobTarget = this.getNiceGroup(group, event.target, col_width); + else if (job.nice_target) jobTarget = '
' + job.nice_target + '
'; - html += '
PROCESS ID
'; - html += '
' + (job.detached_pid || job.pid || '(Unknown)') + '
'; - html += '
'; - - html += '
'; - html += '
JOB STARTED
'; - html += '
'; + let jobStarted = get_nice_date_time(job.time_start, false, true); if ((job.time_start - job.now >= 60) && !event.multiplex && !job.source) { - html += ''; - html += get_nice_date_time(job.time_start, false, true); - html += ''; + jobStarted = `${get_nice_date_time(job.time_start, false, true)}` } - else html += get_nice_date_time(job.time_start, false, true); - html += '
'; - - html += '
JOB COMPLETED
'; - html += '
' + get_nice_date_time(job.time_end, false, true) + '
'; - - html += '
ELAPSED TIME
'; - html += '
' + get_text_from_seconds(job.elapsed, false, false) + '
'; - html += '
'; - html += '
'; html += ''; + let timing = summarize_event_timing(event.timing, event.timezone) + + html += ` +
+
JOB ID:
${job.id}
+
PID:
${(job.detached_pid || job.pid || '(Unknown)')}
+
CAT:
${jobCategory}
+
SOURCE:
${job.source || 'Scheduler'}
+
TARGET:
${jobTarget}
+
START:
${jobStarted}
+
ELAPSED:
${get_text_from_seconds(job.elapsed, false, false)}
+ + +
EVENT:
${eventTitle}
+
ARG:
${encode_entities(job.arg || '(no argument)')}
+
PLUGIN:
${jobPlugin}
+
MEMO:
${job.memo || '(none)'}
+
HOST:
${this.getNiceGroup(null, job.hostname, col_width)}
+
END:
${get_nice_date_time(job.time_end, false, true)}
+
${timing}
+
+
+ ` + // pies html += '
'; @@ -825,76 +796,36 @@ Class.subclass(Page.Base, "Page.JobDetails", { html += '
'; html += '
'; - // fieldset header - html += '
Job Details'; - - // html += '
Abort Job...
'; - - html += '
'; - html += '
JOB ID
'; - html += ``; - - html += '
EVENT NAME
'; - html += ''; - - // html += '
EVENT TIMING
'; - // html += '
' + (event.enabled ? summarize_event_timing(event.timing, event.timezone) : '(Disabled)') + '
'; - // html += '
'; - - html += '
ARGUMENT
'; // hist - html += '
' + encode_entities(job.arg || '(no argument)') + '
'; - html += '
'; - - // html += '
ARGUMENT
'; - // html += '
' + encode_entities(job.arg || '') + '
'; - // html += '
'; - - - html += '
'; - html += '
CATEGORY NAME
'; - html += '
' + this.getNiceCategory(cat, col_width) + '
'; - - html += '
PLUGIN NAME
'; - html += '
' + this.getNicePlugin(plugin, col_width) + '
'; - - html += '
EVENT TARGET
'; - html += '
' + this.getNiceGroup(group, event.target, col_width) + '
'; - html += '
'; - - html += '
'; - html += '
JOB SOURCE
'; - html += `
' + (job.source || 'Scheduler') + '
'; - - html += '
SERVER HOSTNAME
'; - html += '
' + this.getNiceGroup(null, job.hostname, col_width) + '
'; - - html += '
PROCESS ID
'; - html += '
' + job.pid + '
'; - html += '
'; - - html += '
'; - html += '
JOB STARTED
'; - html += '
' + get_nice_date_time(job.time_start, false, true) + '
'; - - html += '
ELAPSED TIME
'; - var elapsed = Math.floor(Math.max(0, app.epoch - job.time_start)); - html += '
' + get_text_from_seconds(elapsed, false, false) + '
'; - - var progress = job.progress || 0; - var nice_remain = 'n/a'; + let eventTitle = `${this.getNiceEvent(job.event_title, col_width)}` + let elapsed = Math.floor(Math.max(0, app.epoch - job.time_start)); + let job_progress = job.progress || 0; + let nice_remain = 'n/a'; if (job.pending && job.when) { nice_remain = 'Retry in ' + get_text_from_seconds(Math.max(0, job.when - app.epoch), true, true) + ''; } - else if ((elapsed >= 10) && (progress > 0) && (progress < 1.0)) { - var sec_remain = Math.floor(((1.0 - progress) * elapsed) / progress); + else if ((elapsed >= 10) && (job_progress > 0) && (job_progress < 1.0)) { + var sec_remain = Math.floor(((1.0 - job_progress) * elapsed) / job_progress); nice_remain = get_text_from_seconds(sec_remain, false, true); } - html += '
REMAINING TIME
'; - html += '
' + nice_remain + '
'; - html += '
'; - html += '
'; - html += ''; + html += ` +
+
JOB ID:
${job.id}
+
PID:
${(job.detached_pid || job.pid || '(Unknown)')}
+
CAT:
${this.getNiceCategory(cat, col_width)}
+
TARGET:
${this.getNiceGroup(group, event.target, col_width) }
+
SOURCE:
${job.source || 'Scheduler'}
+
START:
${get_nice_date_time(job.time_start, false, true) }
+ +
EVENT:
${eventTitle}
+
ARG:
${encode_entities(job.arg || '(no argument)')}
+
PLUGIN:
${this.getNicePlugin(plugin, col_width)}
+
HOST:
${this.getNiceGroup(null, job.hostname, col_width)}
+
ELAPSED TIME:
${get_text_from_seconds(elapsed, false, false)}
+
REMAINING TIME:
${nice_remain}
+
+
+ ` // pies html += '
'; diff --git a/htdocs/js/pages/Schedule.class.js b/htdocs/js/pages/Schedule.class.js index ba1a8d7..815681f 100644 --- a/htdocs/js/pages/Schedule.class.js +++ b/htdocs/js/pages/Schedule.class.js @@ -122,7 +122,7 @@ Class.subclass(Page.Base, "Page.Schedule", { py: "python" }, - setFileEditor: function(fileName) { + setFileEditor: function (fileName) { const self = this let editor = CodeMirror.fromTextArea(document.getElementById("fe_ee_pp_file_content"), { mode: self.extension_map[fileName.split('.').pop()] || 'text', @@ -136,9 +136,9 @@ Class.subclass(Page.Base, "Page.Schedule", { lint: true }) - editor.on('change', function(cm){ + editor.on('change', function (cm) { document.getElementById("fe_ee_pp_file_content").value = cm.getValue(); - }); + }); editor.setSize('52vw', '52vh') @@ -180,7 +180,7 @@ Class.subclass(Page.Base, "Page.Schedule", { get_form_table_row('Name', ``) + get_form_table_spacer() + get_form_table_row('Content', ``) - html +=`` + html += `` setTimeout(() => self.setFileEditor('.text'), 30) // editor needs to wait for a bit for modal window to render @@ -224,7 +224,7 @@ Class.subclass(Page.Base, "Page.Schedule", { get_form_table_row('Name', ``) + get_form_table_spacer() + get_form_table_row('Content', ``) - html += '' + html += '' setTimeout(() => self.setFileEditor(file.name), 30) // editor needs to wait for a bit for modal window to render @@ -621,6 +621,12 @@ Class.subclass(Page.Base, "Page.Schedule", { }, + show_event_stats: function(id) { + // let evt = find_object(app.schedule, {id: id}) + // document.getElementById('fe_event_info').innerHTML = `${evt.title}: category: ${evt.category} , plugin: ${evt.plugin}` + // $('#ex_' + id).toggle() + }, + gosub_events: function (args) { // render table of events with filters and search this.div.removeClass('loading'); @@ -750,29 +756,36 @@ Class.subclass(Page.Base, "Page.Schedule", { } // Scheduled Event page: + let miniButtons = '' - let miniButtons = '
' + if (app.hasPrivilege('create_events')) { + miniButtons += '
' + miniButtons += '
' + } if (app.isAdmin()) { - miniButtons += '
' + miniButtons += '
' } - if (app.hasPrivilege('create_events')) { - miniButtons += '
' - miniButtons += '
' - } + miniButtons += '
' + + let eventView = app.getPref('event_view') || 'details' + let isGrid = eventView === 'grid' || eventView === 'gridall' html += ` -
-
Scheduled Events ${cycleWarning} -
 
-
 
-
 
-
 
-
 
- ${miniButtons} -
+
+
Scheduled Events ${cycleWarning}
+
${miniButtons}
+
+
 
+
 
+
 
+
 
+
 
+
+
+
` // prep events for sort this.events.forEach(function (item) { @@ -780,6 +793,8 @@ Class.subclass(Page.Base, "Page.Schedule", { var group = item.target ? find_object(app.server_groups, { id: item.target }) : null; var plugin = item.plugin ? find_object(app.plugins, { id: item.plugin }) : null; + if(item.enabled && cat.enabled) item.active = true + item.category_title = cat ? cat.title : 'Uncategorized'; item.group_title = group ? group.title : item.target; item.plugin_title = plugin ? plugin.title : 'No Plugin'; @@ -799,29 +814,57 @@ Class.subclass(Page.Base, "Page.Schedule", { } // header center (group by buttons) + cols.headerRight = `
+ - +
` + // searchBar + cols.headerCenter = `
 
` + // render table - var last_group = ''; - - var htmlTab = this.getBasicTable2(this.events, cols, 'event', function (item, idx) { - var actions = [ - 'Run', - 'Edit', - 'Stats', - 'History', - 'Delete', - // 'Delete' - ]; + let last_group = ''; + + let xhtml = ''; + + let events = this.events || []; + + let totalEvents = events.length + + if(eventView === 'grid') { + totalEvents = `${events.filter(e=>e.active).length} active` + } + + var htmlTab = this.getBasicTable2(events, cols, 'event', function (item, idx) { + + let actions; + + if (isGrid) { + actions = [ + 'run', + `history`, + 'delete' + ] + + } + else { + actions = [ + 'Run', + 'Edit', + 'Stats', + 'History', + 'Delete', + // 'Delete' + ]; + } var cat = item.category ? find_object(app.categories, { id: item.category }) : null; var group = item.target ? find_object(app.server_groups, { id: item.target }) : null; @@ -854,7 +897,8 @@ Class.subclass(Page.Base, "Page.Schedule", { if (item.chain_error && !app.event_map[item.chain_error]) chain_error += '' + item.chain_error + '
'; var chain_error_msg = chain_error ? ` ` : ''; - var evt_name = self.getNiceEvent(item, col_width, 'float:left', '  '); + var evt_name = self.getNiceEvent(item, col_width, 'float:left', '  ', isGrid); + if (chain_tooltip.length > 0) evt_name += `  ${chain_error_msg}`; // check if event is has limited time range @@ -862,22 +906,22 @@ Class.subclass(Page.Base, "Page.Schedule", { if (item.start_time && Number(item.start_time) > new Date().valueOf() + 60000) inactiveTitle = 'Schedule will resume at ' + new Date(item.start_time).toLocaleString() if (item.end_time && Number(item.end_time) < new Date().valueOf()) inactiveTitle = 'Schedule expired on ' + new Date(item.end_time).toLocaleString() - let niceTiming = summarize_event_timing(item.timing, item.timezone, !inactiveTitle ? item.ticks : null) + let niceTiming = summarize_event_timing(item.timing, item.timezone, (inactiveTitle || isGrid) ? null : item.ticks) if (inactiveTitle) { niceTiming = `${niceTiming}` - if (item.ticks) niceTiming += ` + ` + if (item.ticks) niceTiming += ` + ` } let now = Date.now() / 1000 - - var tds = [ + + tds = [ '', - '
' + evt_name + '
', + `
' + evt_name + '
', self.getNiceCategory(cat, col_width), self.getNicePlugin(plugin, col_width), self.getNiceGroup(group, item.target, col_width), - niceTiming + chainInfo, + niceTiming + (isGrid ? '' : chainInfo), '' + status_html + '', get_text_from_seconds(now - item.modified, true, true), //modified actions.join(' | ') @@ -894,38 +938,108 @@ Class.subclass(Page.Base, "Page.Schedule", { tds.className += cat.color; } + // group by if (group_by) { + let cur_group = item[group_by + '_title']; tds.className = 'event_group_' + (group_by == 'group' ? item['target'] || 'allgrp' : item[group_by]) + ' ' + tds.className if (cur_group != last_group) { last_group = cur_group; - var insert_html = '
'; - switch (group_by) { - case 'category': insert_html += self.getNiceCategory(cat, 500, args.collapse); break; - case 'plugin': insert_html += self.getNicePlugin(plugin, 500, args.collapse); break; - case 'group': insert_html += self.getNiceGroup(group, item.target, 500, args.collapse); break; + let group_title; + + if (isGrid) { // grid view + switch (group_by) { + case 'category': group_title = self.getNiceCategory(cat, 500, args.collapse); break; + case 'plugin': group_title = self.getNicePlugin(plugin, 500, args.collapse); break; + case 'group': group_title = self.getNiceGroup(group, item.target, 500, args.collapse); break; + } + + // for regular grid - do not show disabled category + if(eventView === 'grid' && group_by === 'category' && !cat.enabled) group_title = null; + + if (group_title) xhtml += `
${group_title}
` + // tds.insertAbove = group_title; + } + else { // table view + let insert_html = '
'; + switch (group_by) { + case 'category': insert_html += self.getNiceCategory(cat, 500, args.collapse); break; + case 'plugin': insert_html += self.getNicePlugin(plugin, 500, args.collapse); break; + case 'group': insert_html += self.getNiceGroup(group, item.target, 500, args.collapse); break; + } + tds.insertAbove = `${insert_html}
`; } - insert_html += '
'; - tds.insertAbove = insert_html; + } // group changed + if (args.collapse) tds.hide = true } // group_by + + let timingTitle = niceTiming; + let timing = niceTiming.length > 20 ? summarize_event_timing_short(item.timing) : tds[5] + + + if(item.ticks) { + timingTitle += `

Extra ticks: ${item.ticks}` + timing += "+" + } + + if(app.chained_jobs[item.id]) { + timingTitle += ('

' + app.chained_jobs[item.id]) + timing += "<"; + } + + let lastStatus = 'event-none' + let xcode = app.state.jobCodes[item.id]; + if (xcode === 0) lastStatus = 'event-success' + if (xcode > 0) lastStatus = 'event-error' + if (xcode === 255) lastStatus = 'event-warning' + + // ${tds[0]} + //
+ let itemVisibility = eventView === 'grid' && (!item.active || args.collapse) ? 'none' : 'true' + // link item to it's group, avoid for disabled event on basic grid view + let itemClass = eventView === 'grid' && !item.active ? '' : tds.className + + xhtml += ` +
+
+
${tds[1]}
+ +
+
+ +
+
${actions.join(' | ')}
+
+ ${timing} +
+
+
+ + ` return tds; }); + if (isGrid) html += ` +
+
${totalEvents} events
+ ${cols.headerCenter} +
${cols.headerRight}
+
+
${xhtml}
` + else html += `
${htmlTab}
` + // table and graph (hide latter by default) - html += ` -
${htmlTab}
-
-
- ` + html += `
` + if (app.hasPrivilege('create_events')) { html += ` - + ` } @@ -956,13 +1070,24 @@ Class.subclass(Page.Base, "Page.Schedule", { update_job_last_runs: function () { // update last run state for all jobs, called when state is updated if (!app.state.jobCodes) return; + let isGrid = app.getPref('event_view') === 'grid' || app.getPref('event_view') == 'gridall' for (var event_id in app.state.jobCodes) { - var last_code = app.state.jobCodes[event_id]; - var status_html = last_code ? ' Error' : ' Success'; - if (last_code == 255) status_html = ' Warning' - // this.div.find('#ss_' + event_id).html(status_html); - document.getElementById('ss_' + event_id).innerHTML = status_html + let last_code = app.state.jobCodes[event_id]; + let status_html; + + if (isGrid) { + status_html = last_code ? 'event-error' : 'event-success' + if (last_code == 255) status_html = 'event-warning' + status_html = `` + } + else { + status_html = last_code ? ' Error' : ' Success'; + if (last_code == 255) status_html = ' Warning' + } + + let statusIcon = document.getElementById('ss_' + event_id) + if (statusIcon) statusIcon.innerHTML = status_html } }, @@ -990,6 +1115,14 @@ Class.subclass(Page.Base, "Page.Schedule", { this.gosub_events(this.args); }, + change_event_view: function (view_type) { + // if( ['Grid', 'Details', 'Grid-All'].indexOf(view_type) < 0 ) view_type = 'Details' + if (['details', 'grid', 'gridall'].indexOf(view_type) < 0) view_type = 'details' + app.setPref('event_view', view_type) + this.gosub_events(this.args); + + }, + toggle_group_by: function () { let args = this.args args.collapse ^= true @@ -1524,7 +1657,7 @@ Class.subclass(Page.Base, "Page.Schedule", { } // check for update delete this.old_event; - if (event.secret_value && typeof event.secret_value === 'string' ) { + if (event.secret_value && typeof event.secret_value === 'string') { delete event.secret_value $('#fe_ee_secret').val('').attr('placeholder', '[*****]') } @@ -1734,9 +1867,9 @@ Class.subclass(Page.Base, "Page.Schedule", { } // Secret - let sph = event.secret_preview ? '[' + event.secret_preview + ']' : ''; + let sph = event.secret_preview ? '[' + event.secret_preview + ']' : ''; html += get_form_table_row('Secret', ``); - html += get_form_table_caption("Specify KEY=VALUE pairs to be mounted as env variables (to this job process)"); + html += get_form_table_caption("Specify KEY=VALUE pairs to be mounted as env variables (to this job process). When using Docker plugin, KEY should be prefixed with DOCKER_ to pass custom variable to docker container. When using SSH plugin, KEY should be prefixed with SSH_ to pass to remote machine."); html += get_form_table_spacer(); // max children @@ -2537,13 +2670,13 @@ Class.subclass(Page.Base, "Page.Schedule", { this.refresh_plugin_params(); }, - - setScriptEditor: function() { - + + setScriptEditor: function () { + let params = this.event.params || {} let el = document.getElementById("fe_ee_pp_script") - if(!el) return + if (!el) return let privs = app.user.privileges; let canEdit = privs.admin || privs.edit_events || privs.create_events; @@ -2585,45 +2718,45 @@ Class.subclass(Page.Base, "Page.Schedule", { gutters: [gutter], lint: lint, extraKeys: { - "F11": (cm) => cm.setOption("fullScreen", !cm.getOption("fullScreen")), - "Esc": (cm) => cm.getOption("fullScreen") ? cm.setOption("fullScreen", false) : null, - "Ctrl-/": (cm) => cm.execCommand('toggleComment') - } - }); + "F11": (cm) => cm.setOption("fullScreen", !cm.getOption("fullScreen")), + "Esc": (cm) => cm.getOption("fullScreen") ? cm.setOption("fullScreen", false) : null, + "Ctrl-/": (cm) => cm.execCommand('toggleComment') + } + }); editor.on('change', (cm) => { el.value = cm.getValue() }) // syntax selector - document.getElementById("fe_ee_pp_lang").addEventListener("change", function(){ + document.getElementById("fe_ee_pp_lang").addEventListener("change", function () { let ln = this.options[this.selectedIndex].value; editor.setOption("gutters", ['']); editor.setOption("lint", false) - if(ln == 'java') {ln = 'text/x-java'} - if(ln == 'scala') {ln = 'text/x-scala'} - if(ln == 'csharp') {ln = 'text/x-csharp'} + if (ln == 'java') { ln = 'text/x-java' } + if (ln == 'scala') { ln = 'text/x-scala' } + if (ln == 'csharp') { ln = 'text/x-csharp' } if (ln == 'sql') { ln = 'text/x-sql' } if (ln == 'dockerfile') { ln = 'text/x-dockerfile' } if (ln == 'toml') { ln = 'text/x-toml' } - if (ln == 'json') { + if (ln == 'json') { ln = 'application/json' editor.setOption("lint", CodeMirror.lint.json) - } + } if (ln == 'props') { ln = 'text/x-properties' } if (ln == 'yaml') { ln = 'text/x-yaml' editor.setOption("gutters", ['CodeMirror-lint-markers']); editor.setOption("lint", CodeMirror.lint.yaml) } - editor.setOption("mode", ln); + editor.setOption("mode", ln); }); // theme - document.getElementById("fe_ee_pp_theme").addEventListener("change", function(){ + document.getElementById("fe_ee_pp_theme").addEventListener("change", function () { var thm = this.options[this.selectedIndex].value; - if(thm === 'default' && app.getPref('theme') === 'dark') thm = 'gruvbox-dark'; - if(thm === 'light') thm = 'default'; + if (thm === 'default' && app.getPref('theme') === 'dark') thm = 'gruvbox-dark'; + if (thm === 'light') thm = 'default'; editor.setOption("theme", thm); }); }, @@ -2725,7 +2858,7 @@ Class.subclass(Page.Base, "Page.Schedule", { refresh_plugin_params: function () { // redraw plugin param area after change $('#d_ee_plugin_params').html(this.get_plugin_params_html()); - this.setScriptEditor(); + this.setScriptEditor(); }, get_random_event: function () {
  Add Event...
 
  Random
  Generate