oxidized-web 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of oxidized-web might be problematic. Click here for more details.

Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.rubocop.yml +22 -2
  4. data/CHANGELOG.md +18 -1
  5. data/README.md +13 -3
  6. data/Rakefile +9 -4
  7. data/docs/configuration.md +90 -0
  8. data/docs/development.md +42 -40
  9. data/lib/oxidized/web/public/scripts/oxidized.js +13 -13
  10. data/lib/oxidized/web/public/weblibs/bootstrap-icons.css +31 -3
  11. data/lib/oxidized/web/public/weblibs/bootstrap.bundle.js +20 -19
  12. data/lib/oxidized/web/public/weblibs/bootstrap.bundle.js.map +1 -1
  13. data/lib/oxidized/web/public/weblibs/bootstrap.css +110 -124
  14. data/lib/oxidized/web/public/weblibs/bootstrap.css.map +1 -1
  15. data/lib/oxidized/web/public/weblibs/bootstrap.js +20 -17
  16. data/lib/oxidized/web/public/weblibs/bootstrap.js.map +1 -1
  17. data/lib/oxidized/web/public/weblibs/buttons.bootstrap5.css +3 -3
  18. data/lib/oxidized/web/public/weblibs/buttons.colVis.js +14 -5
  19. data/lib/oxidized/web/public/weblibs/dataTables.bootstrap5.css +111 -17
  20. data/lib/oxidized/web/public/weblibs/dataTables.buttons.js +25 -7
  21. data/lib/oxidized/web/public/weblibs/dataTables.js +337 -107
  22. data/lib/oxidized/web/public/weblibs/dayjs-plugin-utc.min.js +1 -0
  23. data/lib/oxidized/web/public/weblibs/dayjs.min.js +1 -0
  24. data/lib/oxidized/web/public/weblibs/fonts/bootstrap-icons.woff +0 -0
  25. data/lib/oxidized/web/public/weblibs/fonts/bootstrap-icons.woff2 +0 -0
  26. data/lib/oxidized/web/version.rb +1 -1
  27. data/lib/oxidized/web/views/diffs.haml +6 -7
  28. data/lib/oxidized/web/views/head.haml +4 -0
  29. data/lib/oxidized/web/views/node.haml +3 -2
  30. data/lib/oxidized/web/views/nodes.haml +2 -2
  31. data/lib/oxidized/web/views/stats.haml +2 -2
  32. data/lib/oxidized/web/views/version.haml +1 -2
  33. data/lib/oxidized/web/views/versions.haml +6 -5
  34. data/lib/oxidized/web/webapp.rb +40 -25
  35. data/lib/oxidized/web.rb +71 -26
  36. data/oxidized-web.gemspec +22 -13
  37. data/package-lock.json +37 -25
  38. data/package.json +7 -5
  39. data/spec/spec_helper.rb +1 -0
  40. data/spec/web/node/show_spec.rb +100 -0
  41. data/spec/web/node/version_spec.rb +161 -0
  42. data/spec/{node_spec.rb → web/node_spec.rb} +1 -1
  43. data/spec/{nodes_spec.rb → web/nodes_spec.rb} +1 -1
  44. data/spec/{root_spec.rb → web/root_spec.rb} +1 -1
  45. data/spec/{webapp_spec.rb → web/webapp_spec.rb} +1 -1
  46. data/spec/web_spec.rb +98 -0
  47. metadata +69 -69
  48. data/.rubocop_todo.yml +0 -64
  49. data/spec/node_version_spec.rb +0 -102
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.dayjsPluginUTC=e():t.dayjsPluginUTC=e()}(this,function(){return function(t){var e={};function n(o){if(e[o])return e[o].exports;var r=e[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)n.d(o,r,function(e){return t[e]}.bind(null,r));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function r(t){var e;switch(o(t)){case"string":return/Z$/.test(t)?0:(e=/([+-])(\d{2}):?(\d{2})/.exec(t))&&(+e[3]+60*e[2])*("+"===e[1]?1:-1);case"number":return Number.isNaN(t)?null:Math.abs(t)<16?60*t:t;default:return null}}n.r(e);var i=function(t,e,n){var o=String(t);return!o||o.length>=e?t:"".concat(Array(e+1-o.length).join(n)).concat(t)},s=function(t){var e=Math.abs(t),n=Math.floor(e/60),o=e%60;return"".concat(t<=0?"+":"-").concat(i(n,2,"0"),":").concat(i(o,2,"0"))};function u(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}var f=(new Date).getTimezoneOffset(),a=Date.prototype;function c(t){return 6e4*(t-(arguments.length>1&&void 0!==arguments[1]?arguments[1]:f))}var l=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:new Date,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e.getTimezoneOffset();!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.$d=new Date(e.getTime()-c(n)),this.$timezoneOffset=n}var e,n,o;return e=t,(n=[{key:"getTimezoneOffset",value:function(){return this.$timezoneOffset}},{key:"setTimezoneOffset",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.$timezoneOffset;this.$d.setTime(this.$d.getTime()+c(this.$timezoneOffset,t)),this.$timezoneOffset=t}}])&&u(e.prototype,n),o&&u(e,o),t}();["toDateString","toLocaleString","toLocaleDateString","toLocaleTimeString","setDate","setFullYear","setHours","setMilliseconds","setMinutes","setMonth","setSeconds","setTime","setYear","getDate","getDay","getFullYear","getHours","getMilliseconds","getMinutes","getMonth","getSeconds","getYear"].forEach(function(t){l.prototype[t]=function(){return a[t].apply(this.$d,arguments)}}),["toISOString","toUTCString","toGMTString","toJSON","getUTCDate","getUTCDay","getUTCFullYear","getUTCHours","getUTCMilliseconds","getUTCMinutes","getUTCMonth","getUTCSeconds","valueOf","getTime"].forEach(function(t){l.prototype[t]=function(){return a[t].apply(new Date(this.$d.getTime()+c(this.$timezoneOffset)),arguments)}}),["setUTCDate","setUTCFullYear","setUTCHours","setUTCMilliseconds","setUTCMinutes","setUTCMonth","setUTCSeconds"].forEach(function(t){l.prototype[t]=function(){var e=new Date(this.$d.getTime()+c(this.$timezoneOffset));a[t].apply(e,arguments),e.setTime(e.getTime()-c(this.$timezoneOffset)),this.$d=e}}),["toString","toTimeString"].forEach(function(t){l.prototype[t]=function(){return a[t].apply(this.$d,arguments).replace(/GMT(.*)$/,"GMT".concat(s(this.$timezoneOffset)))}});var d=l,p=!1,h=function(t,e){["clone","add","subtract","startOf"].forEach(function(n){t[n]=function(){var t=this.utcOffset();return e[n].apply(this,arguments).utcOffset(t)}}),t.utc=function(){return this.utcOffset(0)},t.local=function(){return this.utcOffset(-f)},t.utcOffset=function(t){if(void 0===t){var e=this.$d.getTimezoneOffset();return 0===e?0:-e}return null!==r(t)&&(this.$d.setTimezoneOffset(-r(t)),this.init()),this},t.toDate=function(){return new Date(this.$d.getTime())},t.isLocal=function(){return this.$d.getTimezoneOffset()===f},t.isUTC=function(){return 0===this.$d.getTimezoneOffset()},t.$set=function(){for(var t,n=this.$d.getTimezoneOffset(),o=arguments.length,r=new Array(o),i=0;i<o;i++)r[i]=arguments[i];return(t=e.$set).call.apply(t,[this].concat(r)),this.$d instanceof Date&&(this.$d=new d(this.$d,n)),this},t.parse=function(t){e.parse.call(this,t);var n=this.$d,o="string"==typeof t.date?r(t.date):null;this.$d=new d(n,null===o?f:-o),p&&this.local(),this.init()}};e.default=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0;p=!!t.parseToLocal;var o=e.prototype,i=function(){};i.prototype=o;var s=new i;h(s,o),s.constructor=e.constructor,e.prototype=s,n.utc=function(t){var e=this(t);return"string"==typeof t&&null===r(t)&&(e.$d.$timezoneOffset=0),e.utc()}}}])});
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs=e()}(this,(function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",c="month",f="quarter",h="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return"["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return-t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,c),s=n-i<0,u=e.clone().add(r+(s?-1:1),c);return+(-(r+(n-i)/(s?i-u:u-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(t){return{M:c,y:h,w:o,d:a,D:d,h:u,m:s,s:i,ms:r,Q:f}[t]||String(t||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},g="en",D={};D[g]=M;var p="$isDayjsObject",S=function(t){return t instanceof _||!(!t||!t[p])},w=function t(e,n,r){var i;if(!e)return g;if("string"==typeof e){var s=e.toLowerCase();D[s]&&(i=s),n&&(D[s]=n,i=s);var u=e.split("-");if(!i&&u.length>1)return t(u[0])}else{var a=e.name;D[a]=e,i=a}return!r&&i&&(g=i),i||!r&&g},O=function(t,e){if(S(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},b=v;b.l=w,b.i=S,b.w=function(t,e){return O(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=w(t.locale,null,!0),this.parse(t),this.$x=this.$x||t.x||{},this[p]=!0}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(b.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.init()},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return b},m.isValid=function(){return!(this.$d.toString()===l)},m.isSame=function(t,e){var n=O(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return O(t)<this.startOf(e)},m.isBefore=function(t,e){return this.endOf(e)<O(t)},m.$g=function(t,e,n){return b.u(t)?this[e]:this.set(n,t)},m.unix=function(){return Math.floor(this.valueOf()/1e3)},m.valueOf=function(){return this.$d.getTime()},m.startOf=function(t,e){var n=this,r=!!b.u(e)||e,f=b.p(t),l=function(t,e){var i=b.w(n.$u?Date.UTC(n.$y,e,t):new Date(n.$y,e,t),n);return r?i:i.endOf(a)},$=function(t,e){return b.w(n.toDate()[t].apply(n.toDate("s"),(r?[0,0,0,0]:[23,59,59,999]).slice(e)),n)},y=this.$W,M=this.$M,m=this.$D,v="set"+(this.$u?"UTC":"");switch(f){case h:return r?l(1,0):l(31,11);case c:return r?l(1,M):l(0,M+1);case o:var g=this.$locale().weekStart||0,D=(y<g?y+7:y)-g;return l(r?m-D:m+(6-D),M);case a:case d:return $(v+"Hours",0);case u:return $(v+"Minutes",1);case s:return $(v+"Seconds",2);case i:return $(v+"Milliseconds",3);default:return this.clone()}},m.endOf=function(t){return this.startOf(t,!1)},m.$set=function(t,e){var n,o=b.p(t),f="set"+(this.$u?"UTC":""),l=(n={},n[a]=f+"Date",n[d]=f+"Date",n[c]=f+"Month",n[h]=f+"FullYear",n[u]=f+"Hours",n[s]=f+"Minutes",n[i]=f+"Seconds",n[r]=f+"Milliseconds",n)[o],$=o===a?this.$D+(e-this.$W):e;if(o===c||o===h){var y=this.clone().set(d,1);y.$d[l]($),y.init(),this.$d=y.set(d,Math.min(this.$D,y.daysInMonth())).$d}else l&&this.$d[l]($);return this.init(),this},m.set=function(t,e){return this.clone().$set(t,e)},m.get=function(t){return this[b.p(t)]()},m.add=function(r,f){var d,l=this;r=Number(r);var $=b.p(f),y=function(t){var e=O(l);return b.w(e.date(e.date()+Math.round(t*r)),l)};if($===c)return this.set(c,this.$M+r);if($===h)return this.set(h,this.$y+r);if($===a)return y(1);if($===o)return y(7);var M=(d={},d[s]=e,d[u]=n,d[i]=t,d)[$]||1,m=this.$d.getTime()+r*M;return b.w(m,this)},m.subtract=function(t,e){return this.add(-1*t,e)},m.format=function(t){var e=this,n=this.$locale();if(!this.isValid())return n.invalidDate||l;var r=t||"YYYY-MM-DDTHH:mm:ssZ",i=b.z(this),s=this.$H,u=this.$m,a=this.$M,o=n.weekdays,c=n.months,f=n.meridiem,h=function(t,n,i,s){return t&&(t[n]||t(e,r))||i[n].slice(0,s)},d=function(t){return b.s(s%12||12,t,"0")},$=f||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r};return r.replace(y,(function(t,r){return r||function(t){switch(t){case"YY":return String(e.$y).slice(-2);case"YYYY":return b.s(e.$y,4,"0");case"M":return a+1;case"MM":return b.s(a+1,2,"0");case"MMM":return h(n.monthsShort,a,c,3);case"MMMM":return h(c,a);case"D":return e.$D;case"DD":return b.s(e.$D,2,"0");case"d":return String(e.$W);case"dd":return h(n.weekdaysMin,e.$W,o,2);case"ddd":return h(n.weekdaysShort,e.$W,o,3);case"dddd":return o[e.$W];case"H":return String(s);case"HH":return b.s(s,2,"0");case"h":return d(1);case"hh":return d(2);case"a":return $(s,u,!0);case"A":return $(s,u,!1);case"m":return String(u);case"mm":return b.s(u,2,"0");case"s":return String(e.$s);case"ss":return b.s(e.$s,2,"0");case"SSS":return b.s(e.$ms,3,"0");case"Z":return i}return null}(t)||i.replace(":","")}))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(r,d,l){var $,y=this,M=b.p(d),m=O(r),v=(m.utcOffset()-this.utcOffset())*e,g=this-m,D=function(){return b.m(y,m)};switch(M){case h:$=D()/12;break;case c:$=D();break;case f:$=D()/3;break;case o:$=(g-v)/6048e5;break;case a:$=(g-v)/864e5;break;case u:$=g/n;break;case s:$=g/e;break;case i:$=g/t;break;default:$=g}return l?$:b.a($)},m.daysInMonth=function(){return this.endOf(c).$D},m.$locale=function(){return D[this.$L]},m.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=w(t,e,!0);return r&&(n.$L=r),n},m.clone=function(){return b.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},M}(),k=_.prototype;return O.prototype=k,[["$ms",r],["$s",i],["$m",s],["$H",u],["$W",a],["$M",c],["$y",h],["$D",d]].forEach((function(t){k[t[1]]=function(e){return this.$g(e,t[0],t[1])}})),O.extend=function(t,e){return t.$i||(t(e,_,O),t.$i=!0),O},O.locale=w,O.isDayjs=S,O.unix=function(t){return O(1e3*t)},O.en=D[g],O.Ls=D,O.p={},O}));
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Oxidized
4
4
  module API
5
- WEB_VERSION = '0.16.0'
5
+ WEB_VERSION = '0.17.0'
6
6
  end
7
7
  end
@@ -10,9 +10,8 @@
10
10
  %b #{@info[:node]}
11
11
  .row
12
12
  .col-sm-12
13
- - date_version = Time.parse @info[:date]
14
13
  Date of version:
15
- %span.time #{date_version.strftime("%d-%m-%y at %r")}
14
+ %span.time{ epoch: @info[:time].to_i }= @info[:time]
16
15
  .row
17
16
  .col-sm-12
18
17
  Number of lines changed:
@@ -22,7 +21,7 @@
22
21
  .row
23
22
  .col-sm-6
24
23
  - params = "node=#{@info[:node]}&group=#{@info[:group]}&oid=#{@info[:oid]}"
25
- - params = "#{params}&date=#{@info[:date]}&num=#{@info[:num]}"
24
+ - params = "#{params}&epoch=#{@info[:time].to_i}&num=#{@info[:num]}"
26
25
  %form{action: url_for("/node/version/diffs?#{params}"), method: 'post', role: 'form'}
27
26
  .form-group
28
27
  %select.form-select#oid2{name: 'oid2'}
@@ -30,9 +29,9 @@
30
29
  - num = @oids_dates.count + 1
31
30
  - next_id = false
32
31
  - @oids_dates.each do |x|
33
- %option{value: x[:oid]} Version #{num -= 1} (#{time_from_now x[:date]})
32
+ %option{value: x[:oid]} Version #{num -= 1} (#{time_from_now x[:time]})
34
33
  - if (x[:oid].to_s == @info[:oid2]) || (next_id)
35
- - diff2 = {num: num, date: x[:date]}
34
+ - diff2 = {num: num, time: x[:time]}
36
35
  - next_id = false
37
36
  - elsif (x[:oid].to_s == @info[:oid]) && !(@info[:oid2])
38
37
  - next_id = true
@@ -41,8 +40,8 @@
41
40
 
42
41
  .row
43
42
  .col-sm-12
44
- .old_version_title Version #{diff2[:num]} (#{time_from_now diff2[:date]})
45
- .new_version_title Version #{@info[:num]} (#{time_from_now @info[:date]})
43
+ .old_version_title Version #{diff2[:num]} (#{time_from_now diff2[:time]})
44
+ .new_version_title Version #{@info[:num]} (#{time_from_now @info[:time]})
46
45
 
47
46
  .row
48
47
  .col-sm-12
@@ -25,5 +25,9 @@
25
25
  %script{src: url_for('/weblibs/dataTables.buttons.js')}
26
26
  %script{src: url_for('/weblibs/buttons.bootstrap5.js')}
27
27
  %script{src: url_for('/weblibs/buttons.colVis.js')}
28
+ -# Day.js
29
+ %script{src: url_for('/weblibs/dayjs.min.js')}
30
+ %script{src: url_for('/weblibs/dayjs-plugin-utc.min.js')}
31
+ %script dayjs.extend(dayjsPluginUTC.default)
28
32
  -# Oxidized customisation
29
33
  %script{src: url_for('/scripts/oxidized.js')}
@@ -14,7 +14,8 @@
14
14
  %a.link-dark.link-underline-opacity-0{title: 'update',
15
15
  href: url_for("/node/next/#{@data[:full_name]}")}
16
16
  %i.bi.bi-repeat
17
- - out = '';PP.pp(@data,out)
18
17
  %pre.bg-body-tertiary.border.border-secondary-subtle.rounded
19
- = preserve "#{out}"
18
+ %code
19
+ =escape_once(JSON.pretty_generate(@data))
20
+
20
21
 
@@ -42,8 +42,8 @@
42
42
  %td
43
43
  %div{title: node[:status], class: node[:status]}
44
44
  %span{style: 'visibility: hidden'}#{node[:status]}
45
- %td.time= node[:time]
46
- %td.time= node[:mtime]
45
+ %td.time{ epoch: node[:time].to_i }= node[:time]
46
+ %td.time{ epoch: node[:mtime].to_i }= node[:mtime]
47
47
  %td
48
48
  %a.link-dark.link-underline-opacity-0{title: 'Configuration',
49
49
  href: url_for("/node/fetch/#{node[:full_name]}")}
@@ -79,8 +79,8 @@
79
79
  %td
80
80
  %div{title: status, class: status}
81
81
  %span{style: 'visibility: hidden'}#{status}
82
- %td.time= last_success
83
- %td.time= last_failure
82
+ %td.time{ epoch: last_success.to_i }= last_success
83
+ %td.time{ epoch: last_failure.to_i }= last_failure
84
84
 
85
85
  :javascript
86
86
  $(function() {
@@ -13,9 +13,8 @@
13
13
  %a.btn.btn-primary{:href => "#{request.path}?#{request.query_string}&format=text"} raw
14
14
  .row
15
15
  .col-sm-12
16
- - date_version = Time.parse @info[:date]
17
16
  Date of version:
18
- %span.time #{date_version.strftime('%Y-%m-%d %H:%M:%S %Z')}
17
+ %span.time{ epoch: @info[:time].to_i }= @info[:time]
19
18
 
20
19
  .row
21
20
  .col-sm-12
@@ -25,17 +25,18 @@
25
25
  - @data.each do |x|
26
26
  %tr
27
27
  %td #{nb -= 1}
28
- %td #{x[:date]}
29
- %td #{time_from_now x[:date]}
28
+ %td.time{ epoch: x[:time].to_i }= x[:time]
29
+ %td #{time_from_now x[:time]}
30
30
  %td
31
31
  - params = "node=#{@node}&group=#{@group}&oid=#{x[:oid]}"
32
- - params = "#{params}&date=#{x[:date]}&num=#{nb}"
32
+ - params = "#{params}&epoch=#{x[:time].to_i}&num=#{nb}"
33
33
  %a.link-dark.link-underline-opacity-0{title: 'configuration',
34
34
  href: url_for("/node/version/view?#{params}")}
35
35
  %i.bi.bi-cloud-download
36
36
  &nbsp;&nbsp;
37
- %a.link-dark.link-underline-opacity-0{title: 'diff', href: url_for("/node/version/diffs?#{params}")}
38
- %i.bi.bi-file-earmark-diff
37
+ - if nb > 1
38
+ %a.link-dark.link-underline-opacity-0{title: 'Compare with previous version', href: url_for("/node/version/diffs?#{params}")}
39
+ %i.bi.bi-file-earmark-diff
39
40
 
40
41
  :javascript
41
42
  $(function() {
@@ -2,7 +2,6 @@ require 'sinatra/base'
2
2
  require 'sinatra/json'
3
3
  require 'sinatra/url_for'
4
4
  require 'tilt/haml'
5
- require 'pp'
6
5
  require 'htmlentities'
7
6
  require 'charlock_holmes'
8
7
  module Oxidized
@@ -119,7 +118,7 @@ module Oxidized
119
118
  # use this to attach author/email/message to commit
120
119
  put '/node/next/?*?/:node' do
121
120
  node, @json = route_parse :node
122
- opt = JSON.load request.body.read
121
+ opt = JSON.parse request.body.read
123
122
  nodes.next node, opt
124
123
  redirect url_for('/nodes') unless @json
125
124
  @data = 'ok'
@@ -128,7 +127,7 @@ module Oxidized
128
127
 
129
128
  get '/node/show/:node' do
130
129
  node, @json = route_parse :node
131
- @data = nodes.show node
130
+ @data = filter_node_vars(nodes.show(node))
132
131
  out :node
133
132
  end
134
133
 
@@ -158,7 +157,7 @@ module Oxidized
158
157
  node: node,
159
158
  group: params[:group],
160
159
  oid: params[:oid],
161
- date: params[:date],
160
+ time: Time.at(params[:epoch].to_i),
162
161
  num: params[:num]
163
162
  }
164
163
 
@@ -176,7 +175,12 @@ module Oxidized
176
175
  get '/node/version/diffs' do
177
176
  node, @json = route_parse :node
178
177
  @data = nil
179
- @info = { node: node, group: params[:group], oid: params[:oid], date: params[:date], num: params[:num], num2: (params[:num].to_i - 1) }
178
+ @info = { node: node,
179
+ group: params[:group],
180
+ oid: params[:oid],
181
+ time: Time.at(params[:epoch].to_i),
182
+ num: params[:num],
183
+ num2: (params[:num].to_i - 1) }
180
184
  group = nil
181
185
  group = @info[:group] if @info[:group] != ''
182
186
  @oids_dates = nodes.version node, group
@@ -201,7 +205,7 @@ module Oxidized
201
205
  @stat = @data[:stat]
202
206
  @data = @data[:patch]
203
207
  else
204
- @data = 'no available'
208
+ @data = 'No diff available'
205
209
  end
206
210
  @diff = diff_view @data
207
211
  out :diffs
@@ -209,7 +213,7 @@ module Oxidized
209
213
 
210
214
  # used for diff between 2 distant commit
211
215
  post '/node/version/diffs' do
212
- redirect url_for("/node/version/diffs?node=#{params[:node]}&group=#{params[:group]}&oid=#{params[:oid]}&date=#{params[:date]}&num=#{params[:num]}&oid2=#{params[:oid2]}")
216
+ redirect url_for("/node/version/diffs?node=#{params[:node]}&group=#{params[:group]}&oid=#{params[:oid]}&epoch=#{params[:epoch]}&num=#{params[:num]}&oid2=#{params[:oid2]}")
213
217
  end
214
218
 
215
219
  # Taken von Haml 5.0, so it still works in 6.0
@@ -254,26 +258,23 @@ module Oxidized
254
258
  [e.join('.'), json]
255
259
  end
256
260
 
257
- # give the time enlapsed between now and a date
261
+ # give the time elapsed between now and a date (Time object)
258
262
  def time_from_now(date)
259
- if date
260
- # if the + or - is missing, insert +
261
- date.insert(21, '+') unless date =~ /[-+]/
262
- date = DateTime.parse date
263
- now = DateTime.now.new_offset(0)
264
- t = ((now - date) * 24 * 60 * 60).to_i
265
- mm, ss = t.divmod(60)
266
- hh, mm = mm.divmod(60)
267
- dd, hh = hh.divmod(24)
268
- date = if dd.positive?
269
- "#{dd} days #{hh} hours ago"
270
- elsif hh.positive?
271
- "#{hh} hours #{mm} min ago"
272
- else
273
- "#{mm} min #{ss} sec ago"
274
- end
263
+ return "no time specified" if date.nil?
264
+
265
+ raise "time_from_now needs a Time object" unless date.instance_of? Time
266
+
267
+ t = (Time.now - date).to_i
268
+ mm, ss = t.divmod(60)
269
+ hh, mm = mm.divmod(60)
270
+ dd, hh = hh.divmod(24)
271
+ if dd.positive?
272
+ "#{dd} days #{hh} hours ago"
273
+ elsif hh.positive?
274
+ "#{hh} hours #{mm} min ago"
275
+ else
276
+ "#{mm} min #{ss} sec ago"
275
277
  end
276
- date
277
278
  end
278
279
 
279
280
  # method the give diffs in separate view (the old and the new) as in github
@@ -326,6 +327,20 @@ module Oxidized
326
327
  'The text contains binary values - cannot display'
327
328
  end
328
329
  end
330
+
331
+ def filter_node_vars(serialized_node)
332
+ # Make a deep copy of the data, so we do not impact oxidized
333
+ data = Marshal.load(Marshal.dump(serialized_node))
334
+
335
+ hide_node_vars = settings.configuration[:hide_node_vars]
336
+ if data[:vars].is_a?(Hash) && hide_node_vars&.any?
337
+ hide_node_vars.each do |key|
338
+ data[:vars][key] = '<hidden>' if data[:vars].has_key?(key)
339
+ end
340
+ end
341
+
342
+ data
343
+ end
329
344
  end
330
345
  end
331
346
  end
data/lib/oxidized/web.rb CHANGED
@@ -1,38 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
4
+ require 'puma'
2
5
 
3
6
  module Oxidized
4
7
  module API
5
8
  class Web
6
- require 'rack/handler/puma'
9
+ include SemanticLogger::Loggable
10
+
7
11
  attr_reader :thread
8
12
 
13
+ DEFAULT_HOST = '127.0.0.1'
14
+ DEFAULT_PORT = 8888
15
+ DEFAULT_URI_PREFIX = ''
16
+
9
17
  def initialize(nodes, configuration)
10
18
  require 'oxidized/web/webapp'
11
- if configuration.instance_of? Asetus::ConfigStruct
12
- # New configuration syle: extensions.oxidized-web
13
- addr = configuration.listen? || '127.0.0.1'
14
- port = configuration.port? || 8888
15
- uri = configuration.url_prefix? || ''
16
- vhosts = configuration.vhosts? || []
17
- else
18
- # Old configuration stlyle: "rest: 127.0.0.1:8888/prefix"
19
- listen, uri = configuration.split '/'
20
- addr, _, port = listen.rpartition ':'
21
- unless port
22
- port = addr
23
- addr = nil
24
- end
25
- vhosts = []
26
- end
27
- uri = "/#{uri}"
28
- @opts = {
29
- Host: addr,
30
- Port: port
31
- }
19
+ @configuration = self.class.parse_configuration(configuration)
32
20
  WebApp.set :nodes, nodes
33
- WebApp.set :host_authorization, { permitted_hosts: vhosts }
21
+ WebApp.set :configuration, @configuration
22
+ WebApp.set :host_authorization, {
23
+ permitted_hosts: @configuration[:vhosts]
24
+ }
25
+ uri_prefix = @configuration[:uri_prefix]
34
26
  @app = Rack::Builder.new do
35
- map uri do
27
+ map uri_prefix do
36
28
  run WebApp
37
29
  end
38
30
  end
@@ -40,10 +32,63 @@ module Oxidized
40
32
 
41
33
  def run
42
34
  @thread = Thread.new do
43
- Rack::Handler::Puma.run @app, **@opts
44
- exit!
35
+ @server = Puma::Server.new @app
36
+ addr = @configuration[:addr]
37
+ port = @configuration[:port]
38
+ @server.add_tcp_listener addr, port
39
+ logger.info "Oxidized-web server listening on #{addr}:#{port}"
40
+ @server.run.join
45
41
  end
46
42
  end
43
+
44
+ def self.parse_configuration(configuration)
45
+ if configuration.instance_of? Asetus::ConfigStruct
46
+ parse_new_configuration(configuration)
47
+ else
48
+ parse_legacy_configuration(configuration)
49
+ end
50
+ end
51
+
52
+ # New configuration style: extensions.oxidized-web
53
+ def self.parse_new_configuration(configuration)
54
+ hide_node_vars = configuration.hide_node_vars? || []
55
+ unless hide_node_vars.is_a?(Array)
56
+ logger.error "hide_node_vars must be a list of strings"
57
+ hide_node_vars = []
58
+ end
59
+ hide_node_vars = hide_node_vars.map(&:to_sym)
60
+ {
61
+ addr: configuration.listen? || DEFAULT_HOST,
62
+ port: configuration.port? || DEFAULT_PORT,
63
+ uri_prefix: normalize_uri(configuration.url_prefix? ||
64
+ DEFAULT_URI_PREFIX),
65
+ vhosts: configuration.vhosts? || [],
66
+ hide_node_vars: hide_node_vars
67
+ }
68
+ end
69
+
70
+ # Legacy configuration style: "rest: 127.0.0.1:8888/prefix"
71
+ def self.parse_legacy_configuration(configuration)
72
+ listen, uri_prefix = configuration.split('/', 2)
73
+ addr, _, port = listen.rpartition ':'
74
+ unless port
75
+ port = addr
76
+ addr = nil
77
+ end
78
+ {
79
+ addr: addr,
80
+ port: port.to_i,
81
+ uri_prefix: normalize_uri(uri_prefix || DEFAULT_URI_PREFIX),
82
+ vhosts: [],
83
+ hide_node_vars: []
84
+ }
85
+ end
86
+
87
+ def self.normalize_uri(uri_prefix)
88
+ return '/' if uri_prefix.empty?
89
+
90
+ uri_prefix.start_with?('/') ? uri_prefix : "/#{uri_prefix}"
91
+ end
47
92
  end
48
93
  end
49
94
  end
data/oxidized-web.gemspec CHANGED
@@ -20,16 +20,26 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.required_ruby_version = '>= 3.1'
22
22
 
23
- s.add_dependency 'charlock_holmes', '~> 0.7.5'
23
+ # Gemspec strategy
24
+ #
25
+ # For dependency and optional dependencies, we try to set the minimal
26
+ # dependency lower than the Ubuntu Noble or Debian Bookworm package version,
27
+ # so that native packages can be used.
28
+ # We limit the maximal version so that dependabot can warn about new versions
29
+ # and we can test them before activating them in Oxidized.
30
+ #
31
+ # development dependencies are set to the latest minor version of a library
32
+ # and updated after having tested them
33
+
34
+ s.add_dependency 'charlock_holmes', '>= 0.7.5', '< 0.8.0'
24
35
  s.add_dependency 'emk-sinatra-url-for', '~> 0.2'
25
- s.add_dependency 'haml', '~> 6.0'
26
- s.add_dependency 'htmlentities', '~> 4.3'
27
- s.add_dependency 'json', '~> 2.3'
28
- s.add_dependency 'ostruct', '~> 0.6'
29
- s.add_dependency 'oxidized', '~> 0.31'
30
- s.add_dependency 'puma', '>= 3.11.4'
31
- s.add_dependency 'sinatra', '>= 1.4.6'
32
- s.add_dependency 'sinatra-contrib', '>= 1.4.6'
36
+ s.add_dependency 'haml', '>= 6.0.0', '< 6.4.0'
37
+ s.add_dependency 'htmlentities', '>= 4.3.0', '< 4.4.0'
38
+ s.add_dependency 'json', '>= 2.3.0', '< 2.14.0'
39
+ s.add_dependency 'oxidized', '~> 0.34.0'
40
+ s.add_dependency 'puma', '~> 6.6.0'
41
+ s.add_dependency 'sinatra', '~> 4.1.1'
42
+ s.add_dependency 'sinatra-contrib', '~> 4.1.1'
33
43
 
34
44
  s.add_development_dependency 'bundler', '~> 2.2'
35
45
  s.add_development_dependency 'minitest', '~> 5.18'
@@ -37,11 +47,10 @@ Gem::Specification.new do |s|
37
47
  s.add_development_dependency 'rack-test', '~> 2.1'
38
48
  s.add_development_dependency 'rails_best_practices', '~> 1.19'
39
49
  s.add_development_dependency 'rake', '~> 13.0'
40
- s.add_development_dependency 'rubocop', '~> 1.74.0'
41
- s.add_development_dependency 'rubocop-minitest', '~> 0.37.1'
42
- s.add_development_dependency 'rubocop-rails', '~> 2.30.0'
50
+ s.add_development_dependency 'rubocop', '~> 1.78.0'
51
+ s.add_development_dependency 'rubocop-minitest', '~> 0.38.0'
52
+ s.add_development_dependency 'rubocop-rails', '~> 2.32.0'
43
53
  s.add_development_dependency 'rubocop-rake', '~> 0.7.1'
44
54
  s.add_development_dependency 'simplecov', '~> 0.22.0'
45
- s.add_development_dependency 'simplecov-cobertura', '~> 2.1.0'
46
55
  s.add_development_dependency 'simplecov-html', '~> 0.13.1'
47
56
  end
data/package-lock.json CHANGED
@@ -9,11 +9,13 @@
9
9
  "version": "1.0.0",
10
10
  "license": "Apache 2.0",
11
11
  "dependencies": {
12
- "bootstrap": "^5.3.3",
13
- "bootstrap-icons": "^1.11.3",
14
- "datatables.net-bs5": "^2.0.7",
15
- "datatables.net-buttons-bs5": "^3.0.2",
16
- "jquery": "^3.7.1"
12
+ "bootstrap": "~5.3.3",
13
+ "bootstrap-icons": "~1.13.1",
14
+ "datatables.net-bs5": "~2.3.2",
15
+ "datatables.net-buttons-bs5": "~3.2.4",
16
+ "dayjs": "~1.11.13",
17
+ "dayjs-plugin-utc": "~0.1.2",
18
+ "jquery": "~3.7.1"
17
19
  }
18
20
  },
19
21
  "node_modules/@popperjs/core": {
@@ -27,9 +29,9 @@
27
29
  }
28
30
  },
29
31
  "node_modules/bootstrap": {
30
- "version": "5.3.3",
31
- "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
32
- "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
32
+ "version": "5.3.7",
33
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
34
+ "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
33
35
  "funding": [
34
36
  {
35
37
  "type": "github",
@@ -45,9 +47,9 @@
45
47
  }
46
48
  },
47
49
  "node_modules/bootstrap-icons": {
48
- "version": "1.11.3",
49
- "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
50
- "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
50
+ "version": "1.13.1",
51
+ "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
52
+ "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
51
53
  "funding": [
52
54
  {
53
55
  "type": "github",
@@ -60,41 +62,51 @@
60
62
  ]
61
63
  },
62
64
  "node_modules/datatables.net": {
63
- "version": "2.2.2",
64
- "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.2.2.tgz",
65
- "integrity": "sha512-gfODIKE3gpgbVeZy2QGj2Dq9roO6hy00S+k1knklrqlMyAMrh1wt0Q6ryBUM7gU96U77ysbq8dYhxFdmcC/oPQ==",
65
+ "version": "2.3.2",
66
+ "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.3.2.tgz",
67
+ "integrity": "sha512-31TzwIQM0+pr2ZOEOEH6dsHd/WSAl5GDDGPezOHPI3mM2NK4lcDyOoG8xXeWmSbVfbi852LNK5C84fpp4Q+qxg==",
66
68
  "dependencies": {
67
69
  "jquery": ">=1.7"
68
70
  }
69
71
  },
70
72
  "node_modules/datatables.net-bs5": {
71
- "version": "2.2.2",
72
- "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.2.2.tgz",
73
- "integrity": "sha512-0mAbpUf0EpnIEc0RlN6vSrSk9y/+NuReiwDpjHYY3RfzdvH6Lt0+7Q9OU5RIbYxaFxES/z60thxdrw7IUFnBhw==",
73
+ "version": "2.3.2",
74
+ "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.3.2.tgz",
75
+ "integrity": "sha512-1rh0ZTLoiziIQ4oAtgr+IOYVgJfAIceDnbDe535u8kv191pBAdTrKF6ovQO98Xy9mDXLdLNB7QCrLiV/sgPoQw==",
74
76
  "dependencies": {
75
- "datatables.net": "2.2.2",
77
+ "datatables.net": "2.3.2",
76
78
  "jquery": ">=1.7"
77
79
  }
78
80
  },
79
81
  "node_modules/datatables.net-buttons": {
80
- "version": "3.2.2",
81
- "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-3.2.2.tgz",
82
- "integrity": "sha512-+aLTbkbksNmyGpK+8KXbpwYKXYOXvZQR2ySA/8oOQeJU53Xw/67cOHowenEr2d43/RLaz+I0zvV/1Yn+jMRiDw==",
82
+ "version": "3.2.4",
83
+ "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-3.2.4.tgz",
84
+ "integrity": "sha512-anA39/R0kpHA2DOwqEHy/ZMXD5vf4tWmyNO0BnO0kJG7AFNvGTUCWBnBifXYg3G64U6JYpYY+MuTFKIB1/ZMTQ==",
83
85
  "dependencies": {
84
86
  "datatables.net": "^2",
85
87
  "jquery": ">=1.7"
86
88
  }
87
89
  },
88
90
  "node_modules/datatables.net-buttons-bs5": {
89
- "version": "3.2.2",
90
- "resolved": "https://registry.npmjs.org/datatables.net-buttons-bs5/-/datatables.net-buttons-bs5-3.2.2.tgz",
91
- "integrity": "sha512-xjUcbYCBHcUthD1pvo5ghTNjqE6fTMygRrKd0QjBHKQxcqxmHG/m0djD2s6cFBfm8oov132U7U2JCXgQifOoUA==",
91
+ "version": "3.2.4",
92
+ "resolved": "https://registry.npmjs.org/datatables.net-buttons-bs5/-/datatables.net-buttons-bs5-3.2.4.tgz",
93
+ "integrity": "sha512-yX8Ia32P1D9L02XWjisA6a9fFp0LgmzpIcvf/4ty+QG5/qj1tOOqWgsXsxxIY2Uj918WmLJS6VaJabRAMoAqHA==",
92
94
  "dependencies": {
93
95
  "datatables.net-bs5": "^2",
94
- "datatables.net-buttons": "3.2.2",
96
+ "datatables.net-buttons": "3.2.4",
95
97
  "jquery": ">=1.7"
96
98
  }
97
99
  },
100
+ "node_modules/dayjs": {
101
+ "version": "1.11.13",
102
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
103
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
104
+ },
105
+ "node_modules/dayjs-plugin-utc": {
106
+ "version": "0.1.2",
107
+ "resolved": "https://registry.npmjs.org/dayjs-plugin-utc/-/dayjs-plugin-utc-0.1.2.tgz",
108
+ "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg=="
109
+ },
98
110
  "node_modules/jquery": {
99
111
  "version": "3.7.1",
100
112
  "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
data/package.json CHANGED
@@ -12,10 +12,12 @@
12
12
  "author": "The oxidized project",
13
13
  "license": "Apache 2.0",
14
14
  "dependencies": {
15
- "bootstrap": "^5.3.3",
16
- "bootstrap-icons": "^1.11.3",
17
- "datatables.net-bs5": "^2.0.7",
18
- "datatables.net-buttons-bs5": "^3.0.2",
19
- "jquery": "^3.7.1"
15
+ "bootstrap": "~5.3.3",
16
+ "bootstrap-icons": "~1.13.1",
17
+ "datatables.net-bs5": "~2.3.2",
18
+ "datatables.net-buttons-bs5": "~3.2.4",
19
+ "dayjs": "~1.11.13",
20
+ "dayjs-plugin-utc": "~0.1.2",
21
+ "jquery": "~3.7.1"
20
22
  }
21
23
  }
data/spec/spec_helper.rb CHANGED
@@ -7,5 +7,6 @@ SimpleCov.start
7
7
  require 'minitest/autorun'
8
8
  require 'rack/test'
9
9
  require 'oxidized'
10
+ require 'oxidized/web'
10
11
  require 'oxidized/web/webapp'
11
12
  require 'mocha/minitest'