From 24696c00ee02c2c8780fd039f4add41960d1ef59 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 22 Aug 2017 01:13:19 +0200 Subject: [PATCH] Initial Check_MK integration. --- .../controllers/_integration/check_mk.coffee | 46 +++ .../ticket_zoom/sidebar_customer.coffee | 14 +- .../app/controllers/widget/http_log.coffee | 1 + .../controllers/widget/script_snipped.coffee | 24 ++ .../app/lib/base/highlight.pack.js | 3 +- .../app/views/integration/base.jst.eco | 3 +- .../app/views/widget/http_log.jst.eco | 46 +-- .../app/views/widget/script_snipped.jst.eco | 8 + .../application_controller/handles_errors.rb | 4 + .../integration/check_mk_controller.rb | 138 +++++++++ app/models/channel/filter/monitoring_base.rb | 3 - .../user_ticket_counter/background_job.rb | 26 +- config/routes/integration_check_mk.rb | 5 + contrib/cleanup.sh | 1 + .../20170820000001_check_mk_integration.rb | 119 ++++++++ db/seeds/settings.rb | 111 +++++++ lib/models.rb | 2 +- .../integration_check_mk_controller_test.rb | 270 ++++++++++++++++++ test/unit/integration_icinga_test.rb | 19 -- test/unit/integration_nagios_test.rb | 13 - 20 files changed, 780 insertions(+), 76 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/_integration/check_mk.coffee create mode 100644 app/assets/javascripts/app/controllers/widget/script_snipped.coffee create mode 100644 app/assets/javascripts/app/views/widget/script_snipped.jst.eco create mode 100644 app/controllers/integration/check_mk_controller.rb create mode 100644 config/routes/integration_check_mk.rb create mode 100644 db/migrate/20170820000001_check_mk_integration.rb create mode 100644 test/controllers/integration_check_mk_controller_test.rb diff --git a/app/assets/javascripts/app/controllers/_integration/check_mk.coffee b/app/assets/javascripts/app/controllers/_integration/check_mk.coffee new file mode 100644 index 000000000..33c58ecf4 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_integration/check_mk.coffee @@ -0,0 +1,46 @@ +class Index extends App.ControllerIntegrationBase + featureIntegration: 'check_mk_integration' + featureName: 'Check_MK' + featureConfig: 'check_mk_config' + description: [ + ['This service receives http requests from %s and creates tickets with host and service.', 'Check_MK'] + ['If the host and service is recovered again, the ticket will be closed automatically.'] + ] + + render: => + super + new App.SettingsForm( + area: 'Integration::CheckMK' + el: @$('.js-form') + ) + + new App.ScriptSnipped( + el: @$('.js-scriptSnipped') + facility: 'check_mk' + style: 'bash' + content: "#!/bin/bash\n\ncurl -X POST -F 'event_id=123' -F 'host=host1' -F 'service=http' -F 'state=down' #{App.Config.get('http_type')}://#{App.Config.get('fqdn')}/api/v1/integration/check_mk/#{App.Setting.get('check_mk_token')}" + description: [ + ['To enable %s for sending http requests to %s, you need create "%s" in the admin interface if %s.', 'Check_MK', 'Zammad', 'Event Actions', 'Check_MK'] + ] + ) + + new App.HttpLog( + el: @$('.js-log') + facility: 'check_mk' + ) + +class State + @current: -> + App.Setting.get('check_mk_integration') + +App.Config.set( + 'IntegrationCheckMk' + { + name: 'Check_MK' + target: '#system/integration/check_mk' + description: 'An open source monitoring tool.' + controller: Index + state: State + } + 'NavBarIntegrations' +) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_customer.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_customer.coffee index 701a151b6..92bfd8c65 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_customer.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_customer.coffee @@ -1,7 +1,7 @@ class SidebarCustomer extends App.Controller sidebarItem: => return if !@permissionCheck('ticket.agent') - { + items = { head: 'Customer' name: 'customer' icon: 'person' @@ -11,14 +11,16 @@ class SidebarCustomer extends App.Controller name: 'customer-change' callback: @changeCustomer }, - { - title: 'Edit Customer' - name: 'customer-edit' - callback: @editCustomer - }, ] callback: @showCustomer } + return items if @ticket && @ticket.customer_id == 1 + items.actions.push { + title: 'Edit Customer' + name: 'customer-edit' + callback: @editCustomer + } + items showCustomer: (el) => @el = el diff --git a/app/assets/javascripts/app/controllers/widget/http_log.coffee b/app/assets/javascripts/app/controllers/widget/http_log.coffee index 4a625b3bd..43ab07baf 100644 --- a/app/assets/javascripts/app/controllers/widget/http_log.coffee +++ b/app/assets/javascripts/app/controllers/widget/http_log.coffee @@ -25,6 +25,7 @@ class App.HttpLog extends App.Controller render: => @html App.view('widget/http_log')( records: @records + description: @description ) show: (e) => diff --git a/app/assets/javascripts/app/controllers/widget/script_snipped.coffee b/app/assets/javascripts/app/controllers/widget/script_snipped.coffee new file mode 100644 index 000000000..ec2fb1d6e --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/script_snipped.coffee @@ -0,0 +1,24 @@ +class App.ScriptSnipped extends App.Controller + #events: + # 'click .js-record': 'show' + + elements: + '.js-code': 'code' + + + constructor: -> + super + #@fetch() + @records = [] + @render() + + render: => + @html App.view('widget/script_snipped')( + records: @records + description: @description + style: @style + content: @content + ) + + @code.each (i, block) -> + hljs.highlightBlock block \ No newline at end of file diff --git a/app/assets/javascripts/app/lib/base/highlight.pack.js b/app/assets/javascripts/app/lib/base/highlight.pack.js index 302535e3d..fc4c31799 100644 --- a/app/assets/javascripts/app/lib/base/highlight.pack.js +++ b/app/assets/javascripts/app/lib/base/highlight.pack.js @@ -1 +1,2 @@ -!function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return w(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(w(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(o)}else"start"==g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(M);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(M);r;){e+=n(M.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(M)}return e+n(M.substr(t))}function d(){var e="string"==typeof L.sL;if(e&&!x[L.sL])return n(M);var t=e?l(L.sL,M,!0,y[L.sL]):f(M,L.sL.length?L.sL:void 0);return L.r>0&&(B+=t.r),e&&(y[L.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,M=""):e.eB?(k+=n(t)+r,M=""):(k+=r,M=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(M+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(M+=t),k+=b();do L.cN&&(k+=""),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),M="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return M+=t,t.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,L=i||N,y={},k="";for(R=L;R!=N;R=R.parent)R.cN&&(k=h(R.cN,"",!0)+k);var M="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:B,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function f(e,t){t=t||E.languages||Object.keys(x);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(w(n)){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?R[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;E.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?l(n,r,!0):f(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){R[e]=n})}function N(){return Object.keys(x)}function w(e){return e=(e||"").toLowerCase(),x[e]||x[R[e]]}var E={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},x={},R={};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",a={cN:"function",b:c+"\\(",rB:!0,eE:!0,e:"\\("},r={cN:"rule",b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{cN:"value",eW:!0,eE:!0,c:[a,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"id",b:/\#[A-Za-z0-9_-]+/},{cN:"class",b:/\.[A-Za-z0-9_-]+/},{cN:"attr_selector",b:/\[/,e:/\]/,i:"$"},{cN:"pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[a,e.ASM,e.QSM,e.CSSNM]}]},{cN:"tag",b:c,r:0},{cN:"rules",b:"{",e:"}",i:/\S/,c:[e.CBCM,r]}]}});hljs.registerLanguage("scss",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",i={cN:"variable",b:"(\\$"+t+")\\b"},r={cN:"function",b:t+"\\(",rB:!0,eE:!0,e:"\\("},o={cN:"hexcolor",b:"#[0-9A-Fa-f]+"};({cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{cN:"value",eW:!0,eE:!0,c:[r,o,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"important",b:"!important"}]}});return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,r,{cN:"id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",r:0},{cN:"pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},i,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{cN:"value",b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{cN:"value",b:":",e:";",c:[r,i,o,e.CSSNM,e.QSM,e.ASM,{cN:"important",b:"!important"}]},{cN:"at_rule",b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[r,i,e.QSM,e.ASM,o,e.CSSNM,{cN:"preprocessor",b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}});hljs.registerLanguage("xml",function(t){var s="[A-Za-z0-9\\._:-]+",c={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php"},e={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},t.C("",{r:10}),{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[e],starts:{e:"",rE:!0,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[e],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars"]}},c,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},e]}]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",b={cN:"doctag",b:"@[A-Za-z]+"},a={cN:"value",b:"#<",e:">"},n=[e.C("#","$",{c:[b]}),e.C("^\\=begin","^\\=end",{c:[b],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},i={cN:"params",b:"\\(",e:"\\)",k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[t,{b:c}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),r:0}].concat(n);s.c=d,i.c=d;var o="[>?]>",l="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",N=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:d}},{cN:"prompt",b:"^("+o+"|"+l+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:n.concat(N).concat(d)}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},t=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{cN:"property",b:"@"+n},{b:"`",e:"`",eB:!0,eE:!0,sL:"javascript"}];r.c=t;var s=e.inherit(e.TM,{b:n}),i="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(t)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:t.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+i,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:i,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{cN:"attribute",b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"pi",r:10,b:/^\s*['"]use (strict|asm)['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{bK:"import",e:"[;$]",k:"import from as",c:[e.ASM,e.QSM]},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]}],i:/#/}}); \ No newline at end of file +/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d,i.c=d;var l="[>?]>",o="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",w=[{b:/^\s*=>/,starts:{e:"$",c:d}},{cN:"meta",b:"^("+l+"|"+o+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(w).concat(d)}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}}); \ No newline at end of file diff --git a/app/assets/javascripts/app/views/integration/base.jst.eco b/app/assets/javascripts/app/views/integration/base.jst.eco index 272620104..28b3996bb 100644 --- a/app/assets/javascripts/app/views/integration/base.jst.eco +++ b/app/assets/javascripts/app/views/integration/base.jst.eco @@ -10,9 +10,10 @@
<% if @description: %> <% for item in @description: %> -

<%- @T(item[0], item[1], item[2]) %>

+

<%- @T(item...) %>

<% end %> <% end %>
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/widget/http_log.jst.eco b/app/assets/javascripts/app/views/widget/http_log.jst.eco index a94ff96ee..5682e9f4c 100644 --- a/app/assets/javascripts/app/views/widget/http_log.jst.eco +++ b/app/assets/javascripts/app/views/widget/http_log.jst.eco @@ -1,27 +1,31 @@
- -

<%- @T('Recent logs') %>

-
+

<%- @T('Recent logs') %>

+<% if @description: %> + <% for item in @description: %> +

<%- @T(item...) %>

+ <% end %> +<% end %> +
<% if !@records.length: %> - -
<%- @T('No Entries') %> -
+ +
<%- @T('No Entries') %> +
<% else: %> - - - - - +
<%- @T('Direction') %> - <%- @T('Request') %> - <%- @T('Created at') %> -
+ + + + <% for record in @records: %> - - + -
<%- @T('Direction') %> + <%- @T('Request') %> + <%- @T('Created at') %> +
<%- @T(record.direction) %> - <%= record.status %> <%= record.method %> <%= record.url %> - <%- @humanTime(record.created_at) %> +
<%- @T(record.direction) %> + <%= record.status %> <%= record.method %> <%= record.url %> + <%- @humanTime(record.created_at) %> <% end %> -
+ + <% end %> -
+
diff --git a/app/assets/javascripts/app/views/widget/script_snipped.jst.eco b/app/assets/javascripts/app/views/widget/script_snipped.jst.eco new file mode 100644 index 000000000..8325fc920 --- /dev/null +++ b/app/assets/javascripts/app/views/widget/script_snipped.jst.eco @@ -0,0 +1,8 @@ +
+

<%- @T('Usage') %>

+<% if @description: %> + <% for item in @description: %> +

<%- @T(item...) %>

+ <% end %> +<% end %> +
<%- @content %>
diff --git a/app/controllers/application_controller/handles_errors.rb b/app/controllers/application_controller/handles_errors.rb index 10a5b5141..5ada95335 100644 --- a/app/controllers/application_controller/handles_errors.rb +++ b/app/controllers/application_controller/handles_errors.rb @@ -15,22 +15,26 @@ module ApplicationController::HandlesErrors def not_found(e) logger.error e respond_to_exception(e, :not_found) + http_log end def unprocessable_entity(e) logger.error e respond_to_exception(e, :unprocessable_entity) + http_log end def internal_server_error(e) logger.error e respond_to_exception(e, :internal_server_error) + http_log end def unauthorized(e) error = humanize_error(e.message) response.headers['X-Failure'] = error.fetch(:error_human, error[:error]) respond_to_exception(e, :unauthorized) + http_log end private diff --git a/app/controllers/integration/check_mk_controller.rb b/app/controllers/integration/check_mk_controller.rb new file mode 100644 index 000000000..3d9fb6373 --- /dev/null +++ b/app/controllers/integration/check_mk_controller.rb @@ -0,0 +1,138 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +class Integration::CheckMkController < ApplicationController + skip_before_action :verify_csrf_token + before_action :check_configured + + def update + + # check params + raise Exceptions::UnprocessableEntity, 'event_id is missing!' if params[:event_id].blank? + raise Exceptions::UnprocessableEntity, 'state is missing!' if params[:state].blank? + raise Exceptions::UnprocessableEntity, 'host is missing!' if params[:host].blank? + + # search for open ticket + auto_close = Setting.get('check_mk_auto_close') + auto_close_state_id = Setting.get('check_mk_auto_close_state_id') + group_id = Setting.get('check_mk_group_id') + state_recovery_match = '(OK|UP)' + + # check if ticket with host is open + customer = User.lookup(id: 1) + + # follow up detection by meta data + integration = 'check_mk' + open_states = Ticket::State.by_category(:open) + ticket_ids = Ticket.where(state: open_states).order(created_at: :desc).limit(5000).pluck(:id) + ticket_ids_found = [] + ticket_ids.each { |ticket_id| + ticket = Ticket.find_by(id: ticket_id) + next if !ticket + next if !ticket.preferences + next if !ticket.preferences[integration] + next if !ticket.preferences[integration]['host'] + next if ticket.preferences[integration]['host'] != params[:host] + next if ticket.preferences[integration]['service'] != params[:service] + + # found open ticket for service+host + ticket_ids_found.push ticket.id + } + + # new ticket, set meta data + title = "#{params[:host]} is #{params[:state]}" + body = "EventID: #{params[:event_id]} +Host: #{params[:host]} +Service: #{params[:service]} +State: #{params[:state]} +Text: #{params[:text]} +RemoteIP: #{request.remote_ip} +UserAgent: #{request.env['HTTP_USER_AGENT']} +" + + # add article + if params[:state].present? && ticket_ids_found.present? + ticket_ids_found.each { |ticket_id| + ticket = Ticket.find_by(id: ticket_id) + next if !ticket + article = Ticket::Article.create!( + ticket_id: ticket_id, + type_id: Ticket::Article::Type.find_by(name: 'web').id, + sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id, + body: body, + subject: title, + internal: false, + ) + } + if (!auto_close && params[:state].match(/#{state_recovery_match}/i)) || !params[:state].match(/#{state_recovery_match}/i) + render json: { + result: 'ticket already open, added note', + ticket_ids: ticket_ids_found, + } + return + end + end + + # check if service is recovered + if auto_close && params[:state].present? && params[:state].match(/#{state_recovery_match}/i) + if ticket_ids_found.blank? + render json: { + result: 'no open tickets found, ignore action', + } + return + end + state = Ticket::State.lookup(id: auto_close_state_id) + ticket_ids_found.each { |ticket_id| + ticket = Ticket.find_by(id: ticket_id) + next if !ticket + ticket.state_id = auto_close_state_id + ticket.save! + } + render json: { + result: "closed tickets with ids #{ticket_ids_found.join(',')}", + ticket_ids: ticket_ids_found, + } + return + end + + ticket = Ticket.create!( + group_id: group_id, + customer_id: customer.id, + title: title, + preferences: { + check_mk: { + host: params[:host], + service: params[:service], + }, + } + ) + article = Ticket::Article.create!( + ticket_id: ticket.id, + type_id: Ticket::Article::Type.find_by(name: 'web').id, + sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id, + body: body, + subject: title, + internal: false, + ) + + render json: { + result: "new ticket created (ticket id: #{ticket.id})", + ticket_id: ticket.id, + ticket_number: ticket.number, + } + end + + private + + def check_configured + http_log_config facility: 'check_mk' + + if !Setting.get('check_mk_integration') + raise Exceptions::UnprocessableEntity, 'Feature is disable, please contact your admin to enable it!' + end + + if Setting.get('check_mk_token') != params[:token] + raise Exceptions::UnprocessableEntity, 'Invalid token!' + end + end + +end diff --git a/app/models/channel/filter/monitoring_base.rb b/app/models/channel/filter/monitoring_base.rb index a870df71e..c1370d02b 100644 --- a/app/models/channel/filter/monitoring_base.rb +++ b/app/models/channel/filter/monitoring_base.rb @@ -62,8 +62,6 @@ class Channel::Filter::MonitoringBase ticket = Ticket.find_by(id: ticket_id) next if !ticket next if !ticket.preferences - next if !ticket.preferences['integration'] - next if ticket.preferences['integration'] != integration next if !ticket.preferences[integration] next if !ticket.preferences[integration]['host'] next if ticket.preferences[integration]['host'] != result['host'] @@ -88,7 +86,6 @@ class Channel::Filter::MonitoringBase mail[ 'x-zammad-ticket-preferences'.to_sym ] = {} end preferences = {} - preferences['integration'] = integration preferences[integration] = result preferences.each { |key, value| mail[ 'x-zammad-ticket-preferences'.to_sym ][key] = value diff --git a/app/models/observer/ticket/user_ticket_counter/background_job.rb b/app/models/observer/ticket/user_ticket_counter/background_job.rb index 0dc6521e0..a09904d06 100644 --- a/app/models/observer/ticket/user_ticket_counter/background_job.rb +++ b/app/models/observer/ticket/user_ticket_counter/background_job.rb @@ -7,18 +7,22 @@ class Observer::Ticket::UserTicketCounter::BackgroundJob def perform # open ticket count - state_open = Ticket::State.by_category(:open) - tickets_open = Ticket.where( - customer_id: @customer_id, - state_id: state_open, - ).count() + tickets_open = 0 + tickets_closed = 0 + if @customer_id != 1 + state_open = Ticket::State.by_category(:open) + tickets_open = Ticket.where( + customer_id: @customer_id, + state_id: state_open, + ).count() - # closed ticket count - state_closed = Ticket::State.by_category(:closed) - tickets_closed = Ticket.where( - customer_id: @customer_id, - state_id: state_closed, - ).count() + # closed ticket count + state_closed = Ticket::State.by_category(:closed) + tickets_closed = Ticket.where( + customer_id: @customer_id, + state_id: state_closed, + ).count() + end # check if update is needed customer = User.lookup(id: @customer_id) diff --git a/config/routes/integration_check_mk.rb b/config/routes/integration_check_mk.rb new file mode 100644 index 000000000..323201145 --- /dev/null +++ b/config/routes/integration_check_mk.rb @@ -0,0 +1,5 @@ +Zammad::Application.routes.draw do + api_path = Rails.configuration.api_path + + match api_path + '/integration/check_mk/:token', to: 'integration/check_mk#update', via: :post, defaults: { format: 'json' } +end diff --git a/contrib/cleanup.sh b/contrib/cleanup.sh index c057c0dbb..309e91587 100755 --- a/contrib/cleanup.sh +++ b/contrib/cleanup.sh @@ -7,3 +7,4 @@ rm -rf app/assets/javascripts/app/views/layout_ref/ rm app/assets/javascripts/app/controllers/karma.coffee rm app/assets/javascripts/app/controllers/report.coffee rm app/assets/javascripts/app/controllers/report_profile.coffee +rm app/assets/javascripts/app/controllers/_integration/check_mk.coffee diff --git a/db/migrate/20170820000001_check_mk_integration.rb b/db/migrate/20170820000001_check_mk_integration.rb new file mode 100644 index 000000000..8952108b6 --- /dev/null +++ b/db/migrate/20170820000001_check_mk_integration.rb @@ -0,0 +1,119 @@ +class CheckMkIntegration < ActiveRecord::Migration + def up + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Setting.create_if_not_exists( + title: 'Check_MK integration', + name: 'check_mk_integration', + area: 'Integration::Switch', + description: 'Defines if Check_MK (http://mathias-kettner.com/check_mk.html) is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'check_mk_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + permission: ['admin.integration'], + }, + frontend: false + ) + Setting.create_if_not_exists( + title: 'Group', + name: 'check_mk_group_id', + area: 'Integration::CheckMK', + description: 'Defines the group of created tickets.', + options: { + form: [ + { + display: '', + null: false, + name: 'check_mk_group_id', + tag: 'select', + relation: 'Group', + }, + ], + }, + state: 1, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: false + ) + Setting.create_if_not_exists( + title: 'Auto close', + name: 'check_mk_auto_close', + area: 'Integration::CheckMK', + description: 'Defines if tickets should be closed if service is recovered.', + options: { + form: [ + { + display: '', + null: true, + name: 'check_mk_auto_close', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: true, + preferences: { + prio: 3, + permission: ['admin.integration'], + }, + frontend: false + ) + Setting.create_if_not_exists( + title: 'Auto close state', + name: 'check_mk_auto_close_state_id', + area: 'Integration::CheckMK', + description: 'Defines the state of auto closed tickets.', + options: { + form: [ + { + display: '', + null: false, + name: 'check_mk_auto_close_state_id', + tag: 'select', + relation: 'TicketState', + }, + ], + }, + state: 4, + preferences: { + prio: 4, + permission: ['admin.integration'], + }, + frontend: false + ) + Setting.create_if_not_exists( + title: 'Check_MK tolen', + name: 'check_mk_token', + area: 'Core', + description: 'Defines the Check_MK token for allowing updates.', + options: {}, + state: SecureRandom.hex(16), + preferences: { + permission: ['admin.integration'], + }, + frontend: false + ) + end + +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 628516ab6..de3b2d638 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -2847,6 +2847,117 @@ Setting.create_if_not_exists( }, frontend: false ) +Setting.create_if_not_exists( + title: 'Check_MK integration', + name: 'check_mk_integration', + area: 'Integration::Switch', + description: 'Defines if Check_MK (http://mathias-kettner.com/check_mk.html) is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'check_mk_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + permission: ['admin.integration'], + }, + frontend: false +) +Setting.create_if_not_exists( + title: 'Group', + name: 'check_mk_group_id', + area: 'Integration::CheckMK', + description: 'Defines the group of created tickets.', + options: { + form: [ + { + display: '', + null: false, + name: 'check_mk_group_id', + tag: 'select', + relation: 'Group', + }, + ], + }, + state: 1, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: false +) +Setting.create_if_not_exists( + title: 'Auto close', + name: 'check_mk_auto_close', + area: 'Integration::CheckMK', + description: 'Defines if tickets should be closed if service is recovered.', + options: { + form: [ + { + display: '', + null: true, + name: 'check_mk_auto_close', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: true, + preferences: { + prio: 3, + permission: ['admin.integration'], + }, + frontend: false +) +Setting.create_if_not_exists( + title: 'Auto close state', + name: 'check_mk_auto_close_state_id', + area: 'Integration::CheckMK', + description: 'Defines the state of auto closed tickets.', + options: { + form: [ + { + display: '', + null: false, + name: 'check_mk_auto_close_state_id', + tag: 'select', + relation: 'TicketState', + }, + ], + }, + state: 4, + preferences: { + prio: 4, + permission: ['admin.integration'], + }, + frontend: false +) +Setting.create_if_not_exists( + title: 'Check_MK tolen', + name: 'check_mk_token', + area: 'Core', + description: 'Defines the Check_MK token for allowing updates.', + options: {}, + state: SecureRandom.hex(16), + preferences: { + permission: ['admin.integration'], + }, + frontend: false +) + Setting.create_if_not_exists( title: 'LDAP integration', name: 'ldap_integration', diff --git a/lib/models.rb b/lib/models.rb index ce4dcffca..a7a1140fa 100644 --- a/lib/models.rb +++ b/lib/models.rb @@ -242,7 +242,7 @@ returns # update items ActiveRecord::Base.transaction do items_to_update.each { |_id, item| - item.save + item.save! } end } diff --git a/test/controllers/integration_check_mk_controller_test.rb b/test/controllers/integration_check_mk_controller_test.rb new file mode 100644 index 000000000..6bef50bea --- /dev/null +++ b/test/controllers/integration_check_mk_controller_test.rb @@ -0,0 +1,270 @@ +# encoding: utf-8 +require 'test_helper' + +class IntegationCheckMkControllerTest < ActionDispatch::IntegrationTest + setup do + token = SecureRandom.urlsafe_base64(16) + Setting.set('check_mk_token', token) + Setting.set('check_mk_integration', true) + end + + test '01 without token' do + post '/api/v1/integration/check_mk/', {} + assert_response(404) + end + + test '01 invalid token & enabled feature' do + post '/api/v1/integration/check_mk/invalid_token', {} + assert_response(422) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + assert_equal('Invalid token!', result['error']) + end + + test '01 invalid token & disabled feature' do + Setting.set('check_mk_integration', false) + + post '/api/v1/integration/check_mk/invalid_token', {} + assert_response(422) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + assert_equal('Feature is disable, please contact your admin to enable it!', result['error']) + end + + test '02 ticket create & close' do + params = { + event_id: '123', + state: 'down', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert(result['result']) + assert(result['ticket_id']) + assert(result['ticket_number']) + + ticket = Ticket.find(result['ticket_id']) + assert_equal('new', ticket.state.name) + assert_equal(1, ticket.articles.count) + + params = { + event_id: '123', + state: 'up', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert(result['result']) + assert(result['ticket_ids'].include?(ticket.id)) + + ticket.reload + assert_equal('closed', ticket.state.name) + assert_equal(2, ticket.articles.count) + end + + test '02 ticket create & create & auto close' do + params = { + event_id: '123', + state: 'down', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert(result['result']) + assert(result['ticket_id']) + assert(result['ticket_number']) + + ticket = Ticket.find(result['ticket_id']) + assert_equal('new', ticket.state.name) + assert_equal(1, ticket.articles.count) + + params = { + event_id: '123', + state: 'down', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('ticket already open, added note', result['result']) + assert(result['ticket_ids'].include?(ticket.id)) + + ticket.reload + assert_equal('new', ticket.state.name) + assert_equal(2, ticket.articles.count) + + params = { + event_id: '123', + state: 'up', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert(result['result']) + assert(result['ticket_ids'].include?(ticket.id)) + + ticket.reload + assert_equal('closed', ticket.state.name) + assert_equal(3, ticket.articles.count) + end + + test '02 ticket close' do + params = { + event_id: '123', + state: 'up', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('no open tickets found, ignore action', result['result']) + end + + test '02 ticket create & create & no auto close' do + Setting.set('check_mk_auto_close', false) + params = { + event_id: '123', + state: 'down', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert(result['result']) + assert(result['ticket_id']) + assert(result['ticket_number']) + + ticket = Ticket.find(result['ticket_id']) + assert_equal('new', ticket.state.name) + assert_equal(1, ticket.articles.count) + + params = { + event_id: '123', + state: 'down', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('ticket already open, added note', result['result']) + assert(result['ticket_ids'].include?(ticket.id)) + + ticket.reload + assert_equal('new', ticket.state.name) + assert_equal(2, ticket.articles.count) + + params = { + event_id: '123', + state: 'up', + host: 'some host', + service: 'some service', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('ticket already open, added note', result['result']) + assert(result['ticket_ids'].include?(ticket.id)) + + ticket.reload + assert_equal('new', ticket.state.name) + assert_equal(3, ticket.articles.count) + end + + test '02 ticket create & create & auto close - host only' do + params = { + event_id: '123', + state: 'down', + host: 'some host', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert(result['result']) + assert(result['ticket_id']) + assert(result['ticket_number']) + + ticket = Ticket.find(result['ticket_id']) + assert_equal('new', ticket.state.name) + assert_equal(1, ticket.articles.count) + + params = { + event_id: '123', + state: 'down', + host: 'some host', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('ticket already open, added note', result['result']) + assert(result['ticket_ids'].include?(ticket.id)) + + ticket.reload + assert_equal('new', ticket.state.name) + assert_equal(2, ticket.articles.count) + + params = { + event_id: '123', + state: 'up', + host: 'some host', + } + post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params + assert_response(200) + + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert(result['result']) + assert(result['ticket_ids'].include?(ticket.id)) + + ticket.reload + assert_equal('closed', ticket.state.name) + assert_equal(3, ticket.articles.count) + end +end diff --git a/test/unit/integration_icinga_test.rb b/test/unit/integration_icinga_test.rb index 7a765414d..b10e656ff 100644 --- a/test/unit/integration_icinga_test.rb +++ b/test/unit/integration_icinga_test.rb @@ -41,7 +41,6 @@ Comment: [] = ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_p.state.name) assert(ticket_p.preferences) - assert_not(ticket_p.preferences['integration']) assert_not(ticket_p.preferences['icinga']) # RBL check @@ -69,8 +68,6 @@ IPv4: 127.0.0.1=" ticket_0, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_0.state.name) assert(ticket_0.preferences) - assert(ticket_0.preferences['integration']) - assert_equal('icinga', ticket_0.preferences['integration']) assert(ticket_0.preferences['icinga']) assert_equal('apn4711.dc.example.com (Display Name: "apn4711.dc.example.com")', ticket_0.preferences['icinga']['host']) assert_equal('CHECK_RBL CRITICAL - apn4711.dc.example.com BLACKLISTED on 1 server of 38 (ix.dnsbl.example.com)', ticket_0.preferences['icinga']['info']) @@ -102,8 +99,6 @@ IPv4: 127.0.0.1=" ticket_0_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_0_1.state.name) assert(ticket_0_1.preferences) - assert(ticket_0_1.preferences['integration']) - assert_equal('icinga', ticket_0_1.preferences['integration']) assert(ticket_0_1.preferences['icinga']) assert_equal('apn4711.dc.example.com (Display Name: "apn4711.dc.example.com")', ticket_0_1.preferences['icinga']['host']) assert_equal('CHECK_RBL CRITICAL - apn4711.dc.example.com BLACKLISTED on 1 server of 38 (ix.dnsbl.example.com)', ticket_0_1.preferences['icinga']['info']) @@ -135,8 +130,6 @@ IPv4: 127.0.0.1=" ticket_0_2, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('closed', ticket_0_2.state.name) assert(ticket_0_2.preferences) - assert(ticket_0_2.preferences['integration']) - assert_equal('icinga', ticket_0_2.preferences['integration']) assert(ticket_0_2.preferences['icinga']) assert_equal('apn4711.dc.example.com (Display Name: "apn4711.dc.example.com")', ticket_0_2.preferences['icinga']['host']) assert_equal('CHECK_RBL CRITICAL - apn4711.dc.example.com BLACKLISTED on 1 server of 38 (ix.dnsbl.example.com)', ticket_0_2.preferences['icinga']['info']) @@ -173,8 +166,6 @@ Comment: [] = ticket_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_1.state.name) assert(ticket_1.preferences) - assert(ticket_1.preferences['integration']) - assert_equal('icinga', ticket_1.preferences['integration']) assert(ticket_1.preferences['icinga']) assert_equal('host.internal.loc', ticket_1.preferences['icinga']['host']) assert_equal('CPU Load', ticket_1.preferences['icinga']['service']) @@ -209,8 +200,6 @@ Comment: [] = ticket_2, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_2.state.name) assert(ticket_2.preferences) - assert(ticket_2.preferences['integration']) - assert_equal('icinga', ticket_2.preferences['integration']) assert(ticket_2.preferences['icinga']) assert_equal('host.internal.loc', ticket_2.preferences['icinga']['host']) assert_equal('Disk Usage 123', ticket_2.preferences['icinga']['service']) @@ -246,8 +235,6 @@ Comment: [] = ticket_1_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_1_1.state.name) assert(ticket_1_1.preferences) - assert(ticket_1_1.preferences['integration']) - assert_equal('icinga', ticket_1_1.preferences['integration']) assert(ticket_1_1.preferences['icinga']) assert_equal('host.internal.loc', ticket_1_1.preferences['icinga']['host']) assert_equal('CPU Load', ticket_1_1.preferences['icinga']['service']) @@ -284,8 +271,6 @@ Comment: [] = assert_equal(ticket_1.id, ticket_1_2.id) assert_equal('closed', ticket_1_2.state.name) assert(ticket_1_2.preferences) - assert(ticket_1_2.preferences['integration']) - assert_equal('icinga', ticket_1_2.preferences['integration']) assert(ticket_1_2.preferences['icinga']) assert_equal('host.internal.loc', ticket_1_2.preferences['icinga']['host']) assert_equal('CPU Load', ticket_1_2.preferences['icinga']['service']) @@ -318,8 +303,6 @@ Comment: [] = ticket_3, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_3.state.name) assert(ticket_3.preferences) - assert(ticket_3.preferences['integration']) - assert_equal('icinga', ticket_3.preferences['integration']) assert(ticket_3.preferences['icinga']) assert_equal('apn4711.dc.example.com', ticket_3.preferences['icinga']['host']) assert_nil(ticket_3.preferences['icinga']['service']) @@ -354,8 +337,6 @@ Comment: [] = assert_equal(ticket_3.id, ticket_3_1.id) assert_equal('closed', ticket_3_1.state.name) assert(ticket_3_1.preferences) - assert(ticket_3_1.preferences['integration']) - assert_equal('icinga', ticket_3_1.preferences['integration']) assert(ticket_3_1.preferences['icinga']) assert_equal('apn4711.dc.example.com', ticket_3.preferences['icinga']['host']) assert_nil(ticket_3_1.preferences['icinga']['service']) diff --git a/test/unit/integration_nagios_test.rb b/test/unit/integration_nagios_test.rb index 5b4136352..e4ef19189 100644 --- a/test/unit/integration_nagios_test.rb +++ b/test/unit/integration_nagios_test.rb @@ -37,7 +37,6 @@ WARNING - load average: 3.44, 0.99, 0.35 ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_p.state.name) assert(ticket_p.preferences) - assert_not(ticket_p.preferences['integration']) assert_not(ticket_p.preferences['nagios']) # matching sender - CPU Load/host.internal.loc @@ -67,8 +66,6 @@ WARNING - load average: 3.44, 0.99, 0.35 ticket_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_1.state.name) assert(ticket_1.preferences) - assert(ticket_1.preferences['integration']) - assert_equal('nagios', ticket_1.preferences['integration']) assert(ticket_1.preferences['nagios']) assert_equal('host.internal.loc', ticket_1.preferences['nagios']['host']) assert_equal('CPU Load', ticket_1.preferences['nagios']['service']) @@ -101,8 +98,6 @@ WARNING - load average: 3.44, 0.99, 0.35 ticket_2, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_2.state.name) assert(ticket_2.preferences) - assert(ticket_2.preferences['integration']) - assert_equal('nagios', ticket_2.preferences['integration']) assert(ticket_2.preferences['nagios']) assert_equal('host.internal.loc', ticket_2.preferences['nagios']['host']) assert_equal('Disk Usage 123', ticket_2.preferences['nagios']['service']) @@ -136,8 +131,6 @@ WARNING - load average: 3.44, 0.99, 0.35 ticket_1_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_1_1.state.name) assert(ticket_1_1.preferences) - assert(ticket_1_1.preferences['integration']) - assert_equal('nagios', ticket_1_1.preferences['integration']) assert(ticket_1_1.preferences['nagios']) assert_equal('host.internal.loc', ticket_1_1.preferences['nagios']['host']) assert_equal('CPU Load', ticket_1_1.preferences['nagios']['service']) @@ -170,8 +163,6 @@ Additional Info: assert_equal(ticket_1.id, ticket_1_2.id) assert_equal('closed', ticket_1_2.state.name) assert(ticket_1_2.preferences) - assert(ticket_1_2.preferences['integration']) - assert_equal('nagios', ticket_1_2.preferences['integration']) assert(ticket_1_2.preferences['nagios']) assert_equal('host.internal.loc', ticket_1_2.preferences['nagios']['host']) assert_equal('CPU Load', ticket_1_2.preferences['nagios']['service']) @@ -204,8 +195,6 @@ Comment: [] = ticket_3, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) assert_equal('new', ticket_3.state.name) assert(ticket_3.preferences) - assert(ticket_3.preferences['integration']) - assert_equal('nagios', ticket_3.preferences['integration']) assert(ticket_3.preferences['nagios']) assert_equal('apn4711.dc.example.com', ticket_3.preferences['nagios']['host']) assert_nil(ticket_3.preferences['nagios']['service']) @@ -240,8 +229,6 @@ Comment: [] = assert_equal(ticket_3.id, ticket_3_1.id) assert_equal('closed', ticket_3_1.state.name) assert(ticket_3_1.preferences) - assert(ticket_3_1.preferences['integration']) - assert_equal('nagios', ticket_3_1.preferences['integration']) assert(ticket_3_1.preferences['nagios']) assert_equal('apn4711.dc.example.com', ticket_3.preferences['nagios']['host']) assert_nil(ticket_3_1.preferences['nagios']['service'])