cob-cli 2.13.1 → 2.14.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.
Files changed (34) hide show
  1. package/lib/task_lists/customize_copy.js +5 -1
  2. package/package.json +1 -1
  3. package/templates/dashboards/dash/dist/css/app.97b1c4b4.css +8 -0
  4. package/templates/dashboards/dash/dist/dashboard.html +5 -5
  5. package/templates/dashboards/dash/dist/js/{app.f9c19b80.js → app.a1c26814.js} +9 -9
  6. package/templates/dashboards/dash/dist/js/app.a1c26814.js.map +1 -0
  7. package/templates/dashboards/dash/package-lock.json +3918 -5165
  8. package/templates/dashboards/dash/package.json +1 -1
  9. package/templates/dashboards/dash/src/App.vue +108 -64
  10. package/templates/dashboards/dash/src/collector.js +116 -36
  11. package/templates/dashboards/dash/src/components/Attention.vue +30 -0
  12. package/templates/dashboards/dash/src/components/Board.vue +25 -53
  13. package/templates/dashboards/dash/src/components/Dashboard.vue +27 -0
  14. package/templates/dashboards/dash/src/components/Filter.vue +59 -0
  15. package/templates/dashboards/dash/src/components/Kibana.vue +116 -0
  16. package/templates/dashboards/dash/src/components/Label.vue +15 -0
  17. package/templates/dashboards/dash/src/components/Menu.vue +33 -22
  18. package/templates/dashboards/dash/src/components/Totals.vue +38 -52
  19. package/templates/dashboards/dash/src/components/TotalsValue.vue +48 -59
  20. package/templates/dashboards/dash/src/dashboard.html +1 -1
  21. package/templates/dashboards/dash/src/definition_dashboard.json +767 -0
  22. package/templates/dashboards/dash/src/output.css +20103 -76122
  23. package/templates/dashboards/dash/tailwind.config.js +6 -10
  24. package/templates/frontend/common/js/cob/_show_hidden.js +1 -1
  25. package/templates/dashboards/dash/definition_dashboard_v59.json +0 -1
  26. package/templates/dashboards/dash/dist/css/app.2ca409ad.css +0 -8
  27. package/templates/dashboards/dash/dist/js/app.8423eff3.js +0 -188
  28. package/templates/dashboards/dash/dist/js/app.8423eff3.js.map +0 -1
  29. package/templates/dashboards/dash/dist/js/app.f9c19b80.js.map +0 -1
  30. package/templates/dashboards/dash/src/Dashboard.vue +0 -66
  31. package/templates/dashboards/dash/src/components/BoardsNav.vue +0 -23
  32. package/templates/dashboards/dash/src/components/BoardsPage.vue +0 -36
  33. package/templates/dashboards/dash/src/components/Title.vue +0 -21
  34. package/templates/dashboards/dash/src/definition_dashboard_v59.json +0 -394
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <iframe id="kibana" :src="shareLink" width="100%" :onload="updateIFrameStyle()" :class="classes"></iframe>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ props: {
8
+ component: Object,
9
+ userInfo: Object
10
+ },
11
+ data: () => ({
12
+ iFrame: null,
13
+ outputFilter: ""
14
+ }),
15
+ mounted() {
16
+ this.iFrame = this.$el
17
+
18
+ this.inputs.forEach(inputVar => {
19
+ this.$watch("component.vars."+inputVar, this.updateKibanaQuery)
20
+ });
21
+ this.updateKibanaQuery()
22
+
23
+ window.addEventListener("resize", this.updateIFrameStyle);
24
+ this.updateIFrameStyle();
25
+
26
+ window.addEventListener("message", this.processKibanaEvent);
27
+ this.processKibanaEvent();
28
+ },
29
+ computed: {
30
+ options() { return this.component['KibanaCustomize'][0] },
31
+ shareLink() { return this.component['ShareLink'] || "" },
32
+ classes() { return this.options['KibanaClasses'] || "" },
33
+ fixedQuery() { return this.options['InputQueryKibana']|| "" },
34
+ inputs() { return this.options['InputVarKibana'].map(v => v['InputVarKibana']) },
35
+ inputFilter() {
36
+ let filters = this.inputs.filter(v => this.component.vars[v]).map(v => this.component.vars[v]);
37
+ if (this.fixedQuery !== "") filters.push(" AND (" + this.fixedQuery + ")");
38
+ return filters.join(" ")
39
+ },
40
+ outputVar() { return this.options['OutputVarKibana'] || "" },
41
+ },
42
+ methods: {
43
+ updateIFrameStyle() {
44
+ if(!this.iFrame || !this.iFrame.contentWindow || !this.iFrame.contentWindow.document.head) {
45
+ //iFrame do Kibana ainda não está pronta. Voltar a tentar em 100ms
46
+ setTimeout( () => this.updateIFrameStyle(), 100)
47
+ } else {
48
+ // Ajusta tamanho do iFrame, de acordo com a dimensão do conteúdo, quando a aplicação estiver pronta
49
+ if (!this.iFrame.contentWindow.document.getElementsByClassName("application").length && !this.iFrame.contentWindow.document.getElementsByClassName("dashboardViewport").length) {
50
+ //Ainda está a carregar, espera mais um pouco
51
+ setTimeout( () => this.updateIFrameStyle(), 100)
52
+ } else {
53
+ // Visualizações ainda vai estar a carregar e o tamanho vai variando
54
+ // Ir actualizando de 100ms em 100ms até 3s (SlowestLoading estimado),
55
+ // para ter um comportamento optimizado tão instantânio qt possível
56
+ const SlowestLoading = 3000
57
+ for(let t = 100; t < SlowestLoading; t += 100) setTimeout(() => {
58
+ this.iFrame.style.minHeight = this.iFrame.contentWindow.document.body.scrollHeight + "px"
59
+ }, t)
60
+ }
61
+
62
+ // Ajuste ao estilo interno do Kibana, alguns condicionais às classes passadas na iFrame (se ainda não tiver sido feito)
63
+ if(!this.iFrame.contentWindow.document.getElementById("cobKibanaStyle")) {
64
+ var s = document.createElement("style");
65
+ s.id = "cobKibanaStyle";
66
+ s.appendChild(document.createTextNode([
67
+ "html, .kbnWelcomeView { background-color: #ffffff00 !important }"
68
+ , (this.classes.indexOf("kibanaEmbPanelTransparent") != -1 ? ".euiPanel { background-color: #ffffff00 !important; border:none; box-shadow: none }" : "")
69
+ , (this.classes.indexOf("kibanaNoNavMenu") != -1 ? ".kbnTopNavMenu__wrapper { display: none }" : "")
70
+ ,".visLegend__toggle { display: none!important; }"
71
+
72
+ ].join("\n")));
73
+ // Aplica dentro de um try catch, porque a iframe Kibana no arranque tem e deixa de ter document.head e dá um erro. Não é um problema pois voltaremos a passar aqui fruto do resize ainda por fazer.
74
+ try { this.iFrame.contentWindow.document.head.appendChild(s); } catch {}
75
+ }
76
+ }
77
+ },
78
+ processKibanaEvent(event) {
79
+ // Só vale a pena reagir aos eventos de mudança de filtro no Kibana
80
+ if(event && event.data && event.data.filters) {
81
+ var filters = []
82
+ for(let filter of event.data.filters) {
83
+ var queryStr;
84
+ var negateStr = filter.meta.negate ? "-" : "";
85
+ var enabled = !filter.meta.disabled;
86
+
87
+ if(filter.query.query_string) {
88
+ queryStr = filter.query.query_string.query;
89
+ } else if (filter.query.match_phrase) {
90
+ var key = Object.keys(filter.query.match_phrase)[0];
91
+ queryStr = key + ':"' + filter.query.match_phrase[key] + '"';
92
+ }
93
+ if (enabled) filters.push(negateStr + "(" +queryStr + ")");
94
+ };
95
+ this.outputFilter = filters.length > 0 ? filters.join(" AND ") : ""
96
+ this.$set(this.component.vars, this.outputVar, this.outputFilter)
97
+ }
98
+ },
99
+ updateKibanaQuery() {
100
+ if(!this.iFrame || !this.iFrame.contentWindow || !this.iFrame.contentWindow.document.head || !this.iFrame.contentWindow.document.getElementsByClassName("kbnTopNavMenu__wrapper").length) {
101
+ //O Kibana ainda não está pronto. Voltar a tentar em 100ms
102
+ setTimeout(this.updateKibanaQuery, 100)
103
+ } else if(this.inputFilter) {
104
+
105
+ if(this.iFrame.contentWindow.document.getElementsByClassName("euiLoadingChart").length > 0) {
106
+ //O Kibana já está pronto mas ainda está a carregar dados. Voltar a tentar em 100ms
107
+ setTimeout(this.updateKibanaQuery, 100)
108
+ } else {
109
+ console.debug("KIBANA QUERY: ", this.inputFilter.replaceAll("__USERNAME__",this.userInfo.username) );
110
+ this.iFrame.contentWindow.postMessage({"query":{ "query_string":{ "query": this.inputFilter.replaceAll("__USERNAME__",this.userInfo.username) || "*" } }}, '*');
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <div :class="classes" :style="image" v-html="label"></div>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ props: { component: Object },
8
+ computed: {
9
+ options() { return this.component['LabelCustomize'][0] },
10
+ label() { return this.component['Label'] || "" },
11
+ classes() { return this.options['LabelClasses'] || "text-center" },
12
+ image() { return this.options['Image'] ? "background-image: url(" + this.options['Image'] + ");" : "" }
13
+ }
14
+ }
15
+ </script>
@@ -1,30 +1,41 @@
1
1
  <template>
2
- <ul class="space-y-2 text-slate-700 decoration-slate-700">
3
- <li v-for="(menuItem, i) in menuItems"
4
- :key="'menuItem-' + i"
5
- :class="'transition ease-in-out duration-300 rounded-md border border-cobline border-l-2 border-l-sky-600 shadow-sm transform hover:translate-x-0.5 '
6
- + menuItem.style"
2
+ <div :class="classes">
3
+ <a v-for="(line, i) in lines"
4
+ :key="i"
5
+ :class="line.classes"
6
+ :href="line.link"
7
7
  >
8
- <a :href="menuItem.link" class="flex items-center font-light py-1 px-3" >
9
- <span v-html="menuItem.text"/>
10
- </a>
11
- </li>
12
- </ul>
8
+ <span style="margin-right: 20px;">
9
+ <i v-if="line.icon" :class="line.icon" style="margin-right:4px"></i>
10
+ <span v-html="line.text"></span>
11
+ </span>
12
+ <Attention class="absolute right-1" :attentionInfo="line.attention"/>
13
+ </a>
14
+ </div>
13
15
  </template>
14
16
 
15
17
  <script>
16
- export default {
17
- props: {
18
- componentData: Object
19
- },
20
- computed: {
21
- menuItems() {
22
- return this.componentData['Text'].map( m => ({
23
- text: m['Text'],
24
- link: m['Link'],
25
- style: m['Style Text'],
26
- }))
18
+ import Attention from './Attention.vue'
19
+
20
+ export default {
21
+ components: { Attention },
22
+ props: { component: Object },
23
+ computed: {
24
+ options() { return this.component['MenuCustomize'][0] },
25
+ classes() { return this.options['MenuClasses'] || "flex flex-col gap-y-2" },
26
+ lines() {
27
+ let lines = this.component['Text'].map( line => ({
28
+ classes: line["TextCustomize"][0]['TextClasses'] || "transition ease-in-out duration-300 rounded-md border border-gray-300 border-l-2 border-l-sky-600 shadow-sm transform hover:translate-x-0.5 p-2",
29
+ icon: line["TextCustomize"][0]['Icon'] || "",
30
+ text: line['Text'] || "",
31
+ link: line['Link'] || "",
32
+ groups: line["TextCustomize"][0]['GroupVisibility'].map(g => g["GroupVisibility"] && g["GroupVisibility"] ).filter(g => g) || [],
33
+ attention: line["TextCustomize"][0]['AttentionInfo'],
34
+ }))
35
+ // Filter out lines that have groupVisibility AND the user does not have any of the groups
36
+ let userGroups = this.component.userInfo.groups.map(g => g.name)
37
+ return lines.filter(l => l.groups.length == 0 || l.groups.filter(g => userGroups.includes(g)).length != 0 )
38
+ }
27
39
  }
28
40
  }
29
- }
30
41
  </script>
@@ -1,63 +1,49 @@
1
1
  <template>
2
- <table class="w-full table-auto">
3
- <thead v-if="headers.length > 0">
4
- <tr class="uppercase text-xs text-slate-700 tracking-wider underline underline-offset-4">
5
- <td :class="'py-2 ' + headers_style">{{ headers[0] }}</td>
6
- <td v-for="header in headers.slice(1)" :key="header"
7
- :class="'py-2 text-right ' + headers_style">{{ header }}</td>
8
- </tr>
9
- </thead>
10
- <tbody>
11
- <tr v-for="(line, i) in lines" :key="'line'+i"
12
- class="text-slate-700">
13
- <td :class="'py-2 ' + line.style">{{ line.name }}</td>
14
- <td v-for="(value, j) in line.values" :key="'value'+j"
15
- class="py-2 text-right">
16
- <TotalsValue :value-data="value" />
17
- </td>
18
- </tr>
19
- </tbody>
2
+ <table :class="classes">
3
+ <tr v-for="(line, i) in lines" :key="'line'+i" :class="line.lineClasses">
4
+ <td :class="line.titleClasses">
5
+ {{ line.title }}
6
+ </td>
7
+ <td v-for="(value, j) in line.values" :key="'value'+i+'-'+j">
8
+ <TotalsValue :value-data="value"/>
9
+ </td>
10
+ </tr>
20
11
  </table>
21
12
  </template>
22
13
 
23
14
  <script>
24
- import TotalsValue from './TotalsValue.vue'
25
- export default {
26
- components: { TotalsValue },
27
- props: { componentData: Object },
28
- computed: {
29
- headers() {
30
- return this.componentData['Header'][0]['Text'].filter(x => !!x).map(h => h['Text'])
31
- },
32
- headers_style() {
33
- return this.componentData['Header'][0]['Style Header']
15
+ import TotalsValue from './TotalsValue.vue'
16
+
17
+ export default {
18
+ components: { TotalsValue },
19
+ props: {
20
+ component: Object,
21
+ userInfo: Object
34
22
  },
35
- lines() {
36
- return this.componentData['Line'].map( l => ({
37
- name : l['Line'],
38
- style : l['Style Line'],
39
- values: l['Value']
40
- }))
23
+ computed: {
24
+ options() { return this.component['TotalsCustomize'][0] },
25
+ classes() { return this.options['TotalsClasses'] || "w-full table-auto" },
26
+ inputs() { return this.options['InputVarTotals'].map(v => v['InputVarTotals']) },
27
+ inputFilter() { return this.inputs.filter(v => this.component.vars[v]).map(v => this.component.vars[v]).join(" ")},
28
+ lines() {
29
+ return this.component['Line'].map( l => ({
30
+ title : l['Line'] || "",
31
+ lineClasses: l["LineCustomize"][0]["LineClasses"] || "text-right transition ease-in-out ring-sky-600 ring-offset-1 hover:ring-2 rounded-md",
32
+ titleClasses: l["LineCustomize"][0]["TitleClasses"] || "text-left p-2",
33
+ values: l['Value']
34
+ }))}
41
35
  },
42
- valuesGridClass() {
43
- const dynamicClasses = {
44
- 1: "grid-cols-1",
45
- 2: "grid-cols-2",
46
- 3: "grid-cols-3",
47
- 4: "grid-cols-4",
48
- 5: "grid-cols-5",
49
- 6: "grid-cols-6",
50
- 7: "grid-cols-7",
51
- 8: "grid-cols-8",
52
- 9: "grid-cols-9",
53
- 10: "grid-cols-10",
54
- 11: "grid-cols-11",
55
- 12: "grid-cols-12",
56
- none: "grid-cols-none"
36
+ watch: {
37
+ inputFilter(newValue) {
38
+ if(newValue == "") return //PRESSUPOSTO IMPORTANTE: se newValue é vazio é porque estamos em transições (porque usamos sempre um valor, nem que seja *) e o melhor é usar o valor antigo para o valor não mudar momentaneamente (e ainda desperdicar uma pesquisa). Se o pressuposto for quebrado vamos impedir a actualização do inputFilter quando o valor é ""
39
+ this.lines.forEach(l => {
40
+ l.values.forEach(v => {
41
+ let arg = (v.Arg[1] instanceof Object ? v.Arg[1].Arg : v.Arg[1])
42
+ let newFilter = ((arg || "") + " " + newValue.trim()) || "*"
43
+ if (v.dash_info) v.dash_info.changeArgs({query: newFilter.replaceAll("__USERNAME__",this.userInfo.username)})
44
+ });
45
+ });
57
46
  }
58
- // Grid with cols == amount of values
59
- return "grid " + dynamicClasses[this.lines[0].values.length]
60
47
  }
61
48
  }
62
- }
63
49
  </script>
@@ -1,69 +1,58 @@
1
1
  <template>
2
- <div :class="{'animate-pulse':signalChange}">
3
- <a :href="valueData.dash_info.href" :class="valueClass" class="relative inline-flex">
4
- <span v-html="value"/>
2
+ <a v-if="state" :href="link" :class="expandedClasses">
5
3
 
6
- <svg v-if="updating" class="absolute animate-spin -top-1 -right-1 h-2 w-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
7
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
8
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
9
- </svg>
10
- </a>
11
- </div>
12
- </template>
4
+ <Attention :attentionInfo="attention" :classes="attentionClasses" />
13
5
 
14
- <script>
6
+ <span v-html="value"/><span class="" >{{unit}}</span>
15
7
 
16
- export default {
17
- props: {
18
- valueData: Object
19
- },
20
- data () {
21
- return {
22
- signalChange: false
23
- }
24
- },
25
- watch: {
26
- state(newState) {
27
- if(newState == "ready") {
28
- this.signalChange = true;
29
- setTimeout( () => this.signalChange = false,8000)
30
- }
31
- }
32
- },
33
- computed: {
34
- updating() {
35
- return this.valueData.dash_info.state == "updating" || this.valueData.dash_info.state == "loading"
36
- },
37
- value() {
38
- if(this.valueData.dash_info.state == "loading") return "L"
39
- if(this.valueData.dash_info.state == "error") return "E"
40
- if(isNaN(this.valueData.dash_info.value)) {
41
- return this.valueData.dash_info.value
42
- } else {
43
- return new Intl.NumberFormat('en-US', {}).format(this.valueData.dash_info.value)
44
- }
45
- },
46
- state() {
47
- return this.valueData.dash_info.state
48
- },
49
- valueClass() {
50
- let c = "relative transition ease-in-out px-2 py-1 rounded-md text-center font-mono font-semibold transition border ring-offset-1 hover:ring-2"
8
+ <svg v-if="updating" class="absolute animate-spin -top-1 -right-1 h-2 w-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
9
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
10
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
11
+ </svg>
12
+ </a>
13
+ </template>
51
14
 
52
- const lookup = {
53
- "Info": "text-sky-600 border-sky-600 bg-sky-200/10 ring-sky-600",
54
- "Success": "text-lime-500 border-lime-500 bg-lime-200/10 ring-lime-500",
55
- "Warning": "text-amber-500 border-amber-500 bg-amber-200/10 ring-amber-500",
56
- "Important": "text-rose-600 border-rose-600 bg-rose-200/10 ring-rose-600",
15
+ <script>
16
+ import Attention from './Attention.vue'
17
+ const specialClasses = {
18
+ "Info": "border border-sky-600 bg-sky-200/10 ring-sky-600 ",
19
+ "Success": "border border-lime-500 bg-lime-200/10 ring-lime-500 ",
20
+ "Warning": "border border-amber-500 bg-amber-200/10 ring-amber-500 ",
21
+ "Important": "border border-rose-600 bg-rose-200/10 ring-rose-600 ",
22
+ "Gray": "text-gray-400 border-gray-200 bg-gray-200/10 ring-gray-200 ",
23
+ "Default": "inline-block " // para poder conter internamente o Attention sem ser noutra linha
24
+ +"whitespace-nowrap " // Para não partir o texto + attention
25
+ +"font-mono font-semibold " // Estilo default para o texto
26
+ +"px-2 py-1 " // Espaçamento horizontal e vertical
27
+ +"rounded-md " // Border arrendondada
28
+ +"transition ease-in-out ring-sky-600 ring-offset-1 hover:ring-2 ", // Estilo para hover (só funciona em localhost, não sei porquê)
29
+ "S_loading": "border border-amber-500 bg-amber-200/10 ring-amber-500 ", // Igual a Warning
30
+ "S_error": "border border-rose-600 bg-rose-200/10 ring-rose-600 ", // Igual a Important
31
+ }
57
32
 
58
- "Gray": "text-gray-400 border-gray-200 bg-gray-200/10 ring-gray-200",
59
- "Fallback": "text-slate-700 ring-slate-700",
33
+ export default {
34
+ components: { Attention },
35
+ props: { valueData: Object },
36
+ computed: {
37
+ options() { return this.valueData['ValueCustomize'][0] },
38
+ view() { return this.options['View'] },
39
+ attention() { return this.options['AttentionInfo'] },
40
+ attentionClasses() { return this.options['AttentionClasses'] },
41
+ unit() { return this.options['Unit'] },
42
+ state() { return this.valueData.dash_info && this.valueData.dash_info.state || "" },
43
+ updating() { return this.state == "updating" || this.state == "loading" },
44
+ classes() { return (this.options['ValueClasses'] || "Default Info") + " S_"+this.valueData.dash_info.state },
45
+ link() { return this.valueData.dash_info.href + (this.view ? "?&av=" + this.view : "") },
46
+ expandedClasses() { return this.classes.split(/\s/).map(c => specialClasses[c] || c ).join(" ") },
47
+ value() {
48
+ if(this.valueData.dash_info.state == "loading") return "L"
49
+ if(this.valueData.dash_info.state == "error") return "E"
50
+ if(isNaN(this.valueData.dash_info.value)) {
51
+ return this.valueData.dash_info.value
52
+ } else {
53
+ return new Intl.NumberFormat('en-US', {maximumFractionDigits: 0}).format(this.valueData.dash_info.value)
54
+ }
60
55
  }
61
-
62
- if(this.valueData.dash_info.state == "loading") return c + " " + lookup["Warning"]
63
- if(this.valueData.dash_info.state == "error") return c + " " + lookup["Important"]
64
-
65
- return c + " " + (this.valueData.dash_info.value == 0 ? lookup["Gray"] : lookup[this.valueData.style] ? lookup[this.valueData.style] : lookup["Fallback"])
66
56
  }
67
57
  }
68
- }
69
58
  </script>
@@ -18,7 +18,7 @@
18
18
  </head>
19
19
  <body>
20
20
  <% } %>
21
- <style>section.custom-resource{visibility: hidden;opacity:0;}</style>
21
+ <style>section.custom-resource{visibility: hidden;opacity:0;left:0px;right:0px;bottom:0px;padding:0px;}</style>
22
22
  <div id="app"></div>
23
23
  <script>
24
24
  // mimes: .cob-app tem estilos globais que se aplicam a elementos vuetify que mudam o aspecto previsto