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
@@ -7,7 +7,7 @@
7
7
  "build": "vue-cli-service build"
8
8
  },
9
9
  "dependencies": {
10
- "@cob/dashboard-info": "^2.4.1",
10
+ "@cob/dashboard-info": "^2.6.0",
11
11
  "@cob/rest-api-wrapper": "^2.1.6",
12
12
  "tailwindcss": "^3.0.23",
13
13
  "vue": "^2.6.11"
@@ -1,77 +1,121 @@
1
1
  <template>
2
- <div>
3
- <div v-if="state=='Loading'" class="text-center my-20 text-2xl text-slate-500">
2
+ <div class="h-full w-full">
3
+ <div v-if="dashboardState=='Loading'" class="text-center my-20 text-2xl text-slate-500">
4
4
  Loading...
5
5
  </div>
6
- <div v-else-if="state=='Error'" class="text-center my-20 text-2xl text-error">
6
+ <div v-else-if="dashboardState=='Error'" class="text-center my-20 text-2xl text-red-500">
7
7
  {{error}}
8
8
  </div>
9
- <Dashboard v-else :dashboard="dashboard" />
9
+ <Dashboard v-else :dashboard="dashboardParsed" :userInfo="userInfo" />
10
10
  </div>
11
11
  </template>
12
12
 
13
13
  <script>
14
- import axios from 'axios';
15
- import { umLoggedin } from '@cob/rest-api-wrapper';
16
- import { instancesList } from '@cob/dashboard-info';
17
- import Dashboard from './Dashboard.vue'
18
- import {parseDashboard} from './collector.js'
14
+ import axios from 'axios';
15
+ import { umLoggedin } from '@cob/rest-api-wrapper';
16
+ import { instancesList } from '@cob/dashboard-info';
17
+ import { parseDashboard } from './collector.js'
18
+ import Dashboard from './components/Dashboard.vue'
19
19
 
20
- export default {
21
- name: 'App',
22
- components: {
23
- Dashboard
24
- },
25
- data: () => ({
26
- userInfo: {},
27
- state: "Loading",
28
- error:"",
29
- currentDashboardInstance: null,
30
- dashboard: null,
31
- page_title: null
32
- }),
33
- created() {
34
- umLoggedin().then( userInfo => this.userInfo = userInfo )
35
- //At the initial load we get the dashboard instance from the url
36
- this.page_title = document.getElementById("dash").getAttribute('data-name')
37
- this.currentDashboardInstance = instancesList("Dashboard", "page_title.raw:\"" + this.page_title + "\"", 1)
20
+ export default {
21
+ name: 'App',
22
+ components: { Dashboard },
23
+ data: () => ({
24
+ userInfo: null,
25
+ name: null,
26
+ error:"",
27
+ dashboardInstance: null,
28
+ dashboardParsed: null,
29
+ dashboardState: "Loading"
30
+ }),
31
+ created() {
32
+ // At the initial load we get the dashboard instance name from the url
33
+ umLoggedin().then( userInfo => {
34
+ let name = document.getElementsByClassName("custom-resource")[0].getAttribute('data-name')
35
+ this.dashboardInstance = instancesList("Dashboard", this.getDashboardQuery(name , userInfo), 1)
36
+ })
38
37
 
39
- $('section.custom-resource').on('resume', (e, params) => {
40
- //register this callback to every anchor navigation. In these cases we get the dashboard instance from the first param to the callback
41
- this.page_title = params[0]
42
- this.currentDashboardInstance.changeArgs({query:"page_title.raw:\"" + this.page_title + "\""})
43
- });
44
- },
45
- watch: {
46
- 'currentDashboardInstance.state'(state) {
47
- if(state == "loading" || state == "updating") {
48
- this.state = "Loading"
49
- } else if(state == "error") {
50
- this.error = "Error: error getting dashboard"
51
- this.state = "Error"
52
- } else if( this.currentDashboardInstance.value.length > 0) {
53
- this.state = "Loading"
54
- let instanceId = this.currentDashboardInstance.value[0].id
55
- axios.get("/recordm/recordm/instances/" + instanceId)
56
- .then(resp => {
57
- try {
58
- this.dashboard = parseDashboard(resp.data)
59
- this.state = "Ready"
60
- }
61
- catch(e) {
62
- this.error = "Error: error processing dashboard " + instanceId
63
- this.state = "Error"
64
- }
65
- })
66
- .catch( (e) => {
67
- this.error = "Error: error getting dashboard " + instanceId
68
- this.state = "Error"
69
- })
70
- } else {
71
- this.error = "Error: dashboard '" + this.page_title + "' not found"
72
- this.state = "Error"
38
+ // Upon anchor navigation we get the dashboard instance name from the first param to the 'resume' callback.
39
+ $('section.custom-resource').on('resume', (e, params) => {
40
+ //Recheck user (the user might have changed or his groups might have changed after previous load)
41
+ umLoggedin().then( userInfo => {
42
+ this.dashboardInstance.changeArgs({query: this.getDashboardQuery(params[0], userInfo) })
43
+ })
44
+ });
45
+ },
46
+ methods: {
47
+ getDashboardQuery(name,userInfo) {
48
+ this.userInfo = userInfo
49
+ this.name = name
50
+ document.title = "Recordm["+name+"]"
51
+ let groups = userInfo.groups.map(g=> "\"" + g.name + "\"").join(" OR ")
52
+
53
+ let nameQuery = "name.raw:\"" + name + "\" "
54
+ let accessQuery = " (groupaccess.raw:(" + groups + ") OR (-groupaccess:*) )"
55
+ return "(" + nameQuery + accessQuery +") OR id:" + name
56
+ }
57
+ },
58
+ watch: {
59
+ // Monitor state changes to the searching of the Dashboard instance
60
+ 'dashboardInstance.state'(instanceInfoState) {
61
+ if(instanceInfoState == "loading" || instanceInfoState == "updating") {
62
+ this.dashboardState = "Loading"
63
+ } else if(instanceInfoState == "error") {
64
+ // Special treatment for 430 (unauthorized) error:
65
+ if(this.dashboardInstance.errorCode == 403) {
66
+ // check who's the new user:
67
+ umLoggedin().then( userInfo => {
68
+ if(userInfo.username == "anonymous") {
69
+ // If the user is anonymous it means we timed out the cookie validity - reload at the same url
70
+ document.location.reload()
71
+ } else {
72
+ // Otherwise the user changed (in another tab) OR the user groups changed OR the dashboards access groups changed
73
+ // send to root
74
+ document.location = "/"
75
+ }
76
+ })
77
+ } else {
78
+ this.dashboardState = "Error"
79
+ this.error = "Error: error getting dashboard (" + this.dashboardInstance.errorCode + ")"
80
+ }
81
+ } else if( this.dashboardInstance.value.length == 0) {
82
+ this.dashboardState = "Error"
83
+ this.error = "Error: a dashboard '" + this.name + "' was not found for your user"
84
+ }
85
+ },
86
+
87
+ // Monitor value changes to the values of the Dashboard instance
88
+ 'dashboardInstance.value'(newDashboards) {
89
+ if(newDashboards.length == 0) {
90
+ this.dashboardState = "Error"
91
+ this.error = "Error: dashboard '" + this.name + "' was not found for your user"
92
+ return
93
+ }
94
+ //Instance(s) found (from ES) but we still need to get the raw instance of the 1st result(from RM) and parse it
95
+ let firstDashId = newDashboards[0].id
96
+ axios
97
+ .get("/recordm/recordm/instances/" + firstDashId)
98
+ .then(resp => {
99
+ try {
100
+ this.dashboardParsed = parseDashboard(resp.data, this.userInfo)
101
+ this.dashboardState = "Ready"
102
+ }
103
+ catch(e) {
104
+ this.error = "Error: error parsing dashboard " + firstDashId
105
+ this.dashboardState = "Error"
106
+ console.error(e)
107
+ }
108
+ })
109
+ .catch( (e) => {
110
+ if( e.response && e.response.status && e.response.status == 403) {
111
+ this.error = "New authorization required..."
112
+ } else {
113
+ this.error = "Error: error getting dashboard " + firstDashId
114
+ }
115
+ this.dashboardState = "Error"
116
+ console.error(e)
117
+ })
73
118
  }
74
119
  }
75
- }
76
- };
77
- </script>
120
+ };
121
+ </script>
@@ -1,6 +1,5 @@
1
1
  import * as dashFunctions from '@cob/dashboard-info';
2
2
  const linkFunction = (url, icon) => { return { value: icon, href: url, state: undefined, isLink: true } }
3
- // dashFunctions["link"] = linkFunction // does not work on brower after deploy (only on localhost dev)
4
3
 
5
4
  const clone = (obj) => JSON.parse(JSON.stringify(obj))
6
5
 
@@ -12,7 +11,7 @@ function collect(bucket, source) {
12
11
  //Means that found one key in current source
13
12
  if (Array.isArray(bucket[sourceName])) {
14
13
  // Means type of value to collect is array
15
- if( bucket[sourceName].length == 0 || bucket[sourceName][0]["Mark"] == "JUST COPY" ) {
14
+ if(bucket[sourceName].length === 0 || bucket[sourceName][0]["Mark"] === "JUST COPY" ) {
16
15
  //Means the bucket template only specify to get the raw elements that match (empty array in bucket template or having first element signaled that it was originally an empty array)
17
16
  source["Mark"] = "JUST COPY" // Signal it was an empty array
18
17
  source[sourceName] = source.value // Add extra field with original name of the source
@@ -29,13 +28,18 @@ function collect(bucket, source) {
29
28
  // Means it's additional source matches
30
29
  initialBucketCopy = clone(bucket[sourceName][0].Initial_Template) // we use a copy of the previously copied template
31
30
  }
31
+ initialBucketCopy.instanceId = bucket.instanceId //needed to build $file url
32
32
  children.reduce(collect,initialBucketCopy) // collect values from the children
33
33
  initialBucketCopy[sourceName] = source.value // Add extra field with original name of the source
34
34
  bucket[sourceName].push(initialBucketCopy) // Add to bucket collector
35
35
  }
36
36
  } else {
37
37
  // Means it's not an array and we can collect the final value
38
- bucket[sourceName] = source.value;
38
+ if(source.value && source.fieldDefinition.description && source.fieldDefinition.description.indexOf("$file") >= 0) {
39
+ bucket[sourceName] = "/recordm/recordm/instances/" + bucket.instanceId + "/files/" + source.fieldDefinition.id + "/" + source.value
40
+ } else {
41
+ bucket[sourceName] = source.value;
42
+ }
39
43
  }
40
44
  } else if (children.length > 0) {
41
45
  // Means it didn't find a match. Continue looking in the children
@@ -46,49 +50,97 @@ function collect(bucket, source) {
46
50
  return bucket;
47
51
  }
48
52
 
49
- function parseDashboard(raw_dashboard){
53
+ function parseDashboard(raw_dashboard, userInfo){
50
54
  let dash = {
51
- "Page Title": "",
52
- "Grid Columns": "",
53
- "Max Width": "",
54
- "Board Title": [{
55
- "Col Span": "",
56
- "Row Span": "",
55
+ "Name": "",
56
+ "DashboardCustomize": [{
57
+ "Grid": "",
58
+ "Width": "",
59
+ "DashboardClasses": "",
60
+ "Image": "",
61
+ "GroupAccess": [{}]
62
+ }],
63
+ "Board": [{
64
+ "BoardCustomize": [{
65
+ "BoardClasses": "",
66
+ "Image": ""
67
+ }],
57
68
  "Component": []
58
69
  }],
59
70
  };
60
71
 
72
+ dash.instanceId = ""+raw_dashboard.id //needed to build $file url
61
73
  raw_dashboard.fields.reduce(collect, dash);
62
74
 
63
75
  const ComponentsTemplates = {
64
- "Title": {
65
- "Title": ""
76
+ "Label": {
77
+ "LabelCustomize": [{
78
+ "LabelClasses": "",
79
+ "Image": ""
80
+ }],
81
+ "Label": "",
82
+ },
83
+ "Menu": {
84
+ "MenuCustomize": [{
85
+ "MenuClasses": ""
86
+ }],
87
+ "Text": [{
88
+ "Link": "",
89
+ "TextCustomize": [{
90
+ "TextClasses": "",
91
+ "Icon": "",
92
+ "TextAttention": "",
93
+ "GroupVisibility": [{}]
94
+ }],
95
+ }],
66
96
  },
67
97
  "Totals" : {
68
- "Header": [{
69
- "Text": [{}],
70
- "Style Header": ""
98
+ "TotalsCustomize": [{
99
+ "TotalsClasses": "",
100
+ "InputVarTotals": [{}],
71
101
  }],
72
102
  "Line": [{
73
- "Style Line": "",
103
+ "LineCustomize": [{
104
+ "LineClasses": "",
105
+ "TitleClasses": "",
106
+ }],
74
107
  "Value": [{
108
+ "ValueCustomize": [{
109
+ "ValueClasses": "",
110
+ "View": "",
111
+ "ValueAttention": "",
112
+ "AttentionClasses": "",
113
+ "Unit": "",
114
+ }],
75
115
  "Style Value": "",
76
116
  "Arg": [{}]
77
117
  }]
78
- }]
118
+ }],
79
119
  },
80
- "Menu": {
81
- "Text": [{
82
- "Link": "",
83
- "Style Text": ""
84
- }]
120
+ "Kibana": {
121
+ "KibanaCustomize": [{
122
+ "KibanaClasses": "",
123
+ "OutputVarKibana": "",
124
+ "InputVarKibana": [{}],
125
+ "InputQueryKibana": ""
126
+ }],
127
+ "ShareLink": "",
128
+ },
129
+ "Filter": {
130
+ "FilterCustomize": [{
131
+ "FilterClasses": "",
132
+ "Placeholder": ""
133
+ }],
134
+ "OutputVarFilter": "",
85
135
  }
86
136
  }
87
137
 
88
- for( let board of dash["Board Title"]) {
138
+ for( let board of dash["Board"]) {
89
139
  let componentsList = clone([])
90
140
  for( let component of board["Component"]) {
141
+ if(component["Component"] == null) continue
91
142
  let componentTemplate = clone(ComponentsTemplates[component["Component"]])
143
+ componentTemplate.instanceId = ""+raw_dashboard.id //needed to $build file url
92
144
  component.fields.reduce(collect,componentTemplate)
93
145
  componentTemplate["Component"] = component.Component
94
146
  componentsList.push(componentTemplate)
@@ -96,28 +148,56 @@ function parseDashboard(raw_dashboard){
96
148
  board["Component"] = componentsList
97
149
  }
98
150
 
99
- // remove all Initial_Templates added
100
- dash = JSON.parse(JSON.stringify(dash, (k,v) => (k === 'Initial_Template')? undefined : v))
151
+
152
+
153
+ // remove all 'Initial_Templates' and 'instanceId' added for processing
154
+ dash = JSON.parse(JSON.stringify(dash, (k,v) => (k === 'Initial_Template' || k === 'instanceId')? undefined : v))
155
+ dash.vars = {} //Available to every components in component.vars
156
+
157
+ // Add extra info to structure
158
+ dash["Board"].forEach(b => b.Component.forEach(c => {
159
+ // Add user info for permission evaluations
160
+ c.userInfo = userInfo
161
+ c.vars = dash.vars
101
162
 
102
- // replace values in Totals by dashboard-info
103
- dash["Board Title"].forEach(b => b.Component.forEach(c => {
104
- if (c.Component == "Totals") {
163
+ if (c.Component === "Menu") {
164
+ c.Text.forEach(t => {
165
+ // If Attention is configured for this menu line then add attention status as user check
166
+ if(t["TextCustomize"][0]["TextAttention"]) {
167
+ t["TextCustomize"][0].AttentionInfo = dashFunctions.instancesList("Dashboard-Attention","name.raw:" + t["TextCustomize"][0]["TextAttention"],1,0,{validity:30})
168
+ }
169
+ })
170
+ } else if (c.Component === "Totals") {
105
171
  c.Line.forEach(l => {
106
172
  l.Value = l.Value.map(v => {
107
- if(v.Arg[2] && v.Arg[2].startsWith("{")) {
173
+ if(v.Arg[2] && (v.Arg[2]+"").startsWith("{")) {
108
174
  v.Arg[2] = JSON.parse(v.Arg[2])
109
175
  }
110
- const valueFunction = v.Value != "link" ? dashFunctions[v.Value] : linkFunction
111
- return ({
112
- dash_info: valueFunction.apply(this, v['Arg'].map( a => a['Arg'])), // Return DashInfo, which is used by the component
113
- style: v["Style Value"]
114
- })
176
+ // If Attention is configured for this value line then add attention status as user check
177
+ if(v["ValueCustomize"][0]["ValueAttention"]) {
178
+ v["ValueCustomize"][0].AttentionInfo = dashFunctions.instancesList("Dashboard-Attention","name.raw:" + v["ValueCustomize"][0]["ValueAttention"],1,0,{validity:10})
179
+ }
180
+
181
+ if(v.Value === 'Label') {
182
+ v.dash_info = {value: v.Arg[0].Arg, state:"ready"}
183
+ } else if(v.Value === 'link') {
184
+ v.dash_info = { value: icon, href: url, state: undefined, isLink: true }
185
+ } else {
186
+ // add dash-info values in Totals
187
+ v.dash_info = dashFunctions[v.Value].apply(this, v['Arg'].map( a =>
188
+ a['Arg'].replaceAll("__USERNAME__",c.userInfo.username)
189
+ )) // Return DashInfo, which is used by the component
190
+ }
191
+ return v
115
192
  })
116
193
  })
194
+ }else if (c.Component === "Kibana") {
195
+ if (c["KibanaCustomize"][0]["InputQueryKibana"] !== null) {
196
+ c["KibanaCustomize"][0]["InputQueryKibana"] = c["KibanaCustomize"][0]["InputQueryKibana"].replaceAll("__USERNAME__", c.userInfo.username)
197
+ }
117
198
  }
118
199
  }))
119
200
  return dash
120
-
121
201
  }
122
202
 
123
- export { parseDashboard, clone, collect }
203
+ export { parseDashboard, clone, collect }
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <i v-if="attentionClasses" :class="attentionClasses" style="line-height: inherit;"></i>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ props: {
8
+ attentionInfo: Object,
9
+ classes: String
10
+ },
11
+ computed: {
12
+ inputClasses() { return this.classes || "fa-solid fa-circle pr-1 animate-pulse text-lg align-middle" },
13
+ attentionClasses() {
14
+ let attentionInfo = this.attentionInfo
15
+ if(attentionInfo && attentionInfo.value && attentionInfo.value[0]) {
16
+ let severity = attentionInfo.value[0].severity && attentionInfo.value[0].severity[0] || 0
17
+ if (severity == 0) return ""
18
+
19
+ let baseColor = severity > 0 ? "lime" : "red"
20
+ let severityAbs = Math.abs(severity)
21
+ let colorIntensity = severityAbs > 4 ? 500 : severityAbs * 100
22
+ let valenceColor = baseColor + (colorIntensity ? "-" + colorIntensity: "")
23
+ return this.inputClasses + " " + "text-" + valenceColor
24
+ } else {
25
+ return ""
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
@@ -1,61 +1,33 @@
1
1
  <template>
2
- <li :class="wrapperClass">
3
- <div class="py-4 px-5 space-y-2">
4
- <slot></slot>
5
- </div>
6
- </li>
2
+ <div :class="classes" :style="image" >
3
+ <template v-for="(item, i) in components">
4
+ <Label v-if="item['Component'] == 'Label'" :component="item" :key="i" />
5
+ <Menu v-if="item['Component'] == 'Menu'" :component="item" :key="i" />
6
+ <Totals v-if="item['Component'] == 'Totals'" :component="item" :key="i" :userInfo="userInfo"/>
7
+ <Kibana v-if="item['Component'] == 'Kibana'" :component="item" :key="i" :userInfo="userInfo"/>
8
+ <Filtro v-if="item['Component'] == 'Filter'" :component="item" :key="i" />
9
+ </template>
10
+ </div>
7
11
  </template>
8
12
 
9
13
  <script>
10
- export default {
11
- props: {
12
- rowSpan: String,
13
- colSpan: String,
14
- title: String,
15
- showTitle: Boolean
16
- },
17
- computed: {
18
- wrapperClass() {
19
- const c = "w-full bg-cobbg rounded-md border border-cobline" // hover:ring-1 ring-slate-400/50 ring-offset-1;
14
+ import Label from './Label.vue'
15
+ import Menu from './Menu.vue'
16
+ import Totals from './Totals.vue'
17
+ import Kibana from './Kibana.vue'
18
+ import Filtro from './Filter.vue'
20
19
 
21
- // Force possible dynamic classes to be loaded
22
- const dynamicRowClasses = {
23
- 1: "md:row-span-1",
24
- 2: "md:row-span-2",
25
- 3: "md:row-span-3",
26
- 4: "md:row-span-4",
27
- 5: "md:row-span-5",
28
- 6: "md:row-span-6",
29
- 7: "md:row-span-7",
30
- 8: "md:row-span-8",
31
- 9: "md:row-span-9",
32
- 10: "md:row-span-10",
33
- 11: "md:row-span-11",
34
- 12: "md:row-span-12",
35
- "full": "md:row-span-full"
36
- }
37
- const rspan = this.rowSpan ? " " + dynamicRowClasses[this.rowSpan] : "";
38
-
39
- // Force possible dynamic classes to be loaded
40
- const dynamicColClasses = {
41
- 1: "md:col-span-1",
42
- 2: "md:col-span-2",
43
- 3: "md:col-span-3",
44
- 4: "md:col-span-4",
45
- 5: "md:col-span-5",
46
- 6: "md:col-span-6",
47
- 7: "md:col-span-7",
48
- 8: "md:col-span-8",
49
- 9: "md:col-span-9",
50
- 10: "md:col-span-10",
51
- 11: "md:col-span-11",
52
- 12: "md:col-span-12",
53
- "full": "md:col-span-full"
54
- }
55
- const cspan = this.colSpan ? " " + dynamicColClasses[this.colSpan] : "";
56
-
57
- return c + rspan + cspan;
20
+ export default {
21
+ components: { Label, Menu, Totals, Kibana, Filtro },
22
+ props: {
23
+ board: Object,
24
+ userInfo: Object
25
+ },
26
+ computed: {
27
+ options() { return this.board['BoardCustomize'][0] },
28
+ components() { return this.board['Component'] },
29
+ classes() { return this.options['BoardClasses'] || "md:col-span-4 rounded-md border border-gray-300 p-4 m-1" },
30
+ image() { return this.options['Image'] ? "background-image: url(" + this.options['Image'] + ");" : "" }
58
31
  }
59
32
  }
60
- }
61
33
  </script>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <div :class="classes" :style="image" >
3
+ <div :class="width + ' ' + grid">
4
+ <Board v-for="(board,i) in boards" :board="board" :key="i" :userInfo="userInfo"/>
5
+ </div>
6
+ </div>
7
+ </template>
8
+
9
+ <script>
10
+ import Board from './Board.vue'
11
+
12
+ export default {
13
+ components: { Board },
14
+ props: {
15
+ dashboard: Object,
16
+ userInfo: Object
17
+ },
18
+ computed: {
19
+ options() { return this.dashboard['DashboardCustomize'][0] },
20
+ boards() { return this.dashboard['Board'] },
21
+ classes() { return this.options['DashboardClasses'] || "h-full bg-cover bg-center overflow-auto p-3" },
22
+ width() { return this.options['Width'] || "max-w-6xl mx-auto" },
23
+ grid() { return this.options['Grid'] || "grid grid-flow-row-dense md:grid-cols-12" },
24
+ image() { return this.options['Image'] ? "background-image: url(" + this.options['Image'] + ");" : "" }
25
+ }
26
+ }
27
+ </script>
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <div class="flex h-fit pb-0 justify-center items-center">
3
+ <textarea :class="classes"
4
+ v-model="inputContent"
5
+ ref="textarea"
6
+ @keyup.enter="applyFilter"
7
+ @focus="resize"
8
+ @keyup="resize"
9
+ :placeholder="placeholder"
10
+ ></textarea>
11
+ <button @click="applyFilter" type="submit" class="max-h-11 p-2.5 ml-2 text-sm font-medium text-white bg-blue-700 rounded-lg border border-blue-700 hover:bg-blue-800">
12
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
13
+ </button>
14
+ </div>
15
+ </template>
16
+
17
+ <script>
18
+ export default {
19
+ props: { component: Object },
20
+ data: () => ({
21
+ inputContent: "",
22
+ activeFilter: ""
23
+ }),
24
+ created() {
25
+ this.$nextTick(() => {
26
+ this.applyFilter()
27
+ this.resize()
28
+ })
29
+ },
30
+ updated() {
31
+ // Necessário para quando fazemos back para uma página com o inputContent preenchido mas que não está actualizada nas variáveis
32
+ if(this.activeFilter != this.component.vars[this.outputVar]) {
33
+ this.applyFilter()
34
+ }
35
+ },
36
+ computed: {
37
+ options() { return this.component['FilterCustomize'][0] },
38
+ outputVar() { return this.component['OutputVarFilter'] || "" },
39
+ placeholder() { return this.options['Placeholder'] || "Pesquisar ..." },
40
+ classes() { return this.options['FilterClasses'] || "w-full max-w-xs resize-none min-h-min h-min border border-slate-300 rounded-md py-2 px-2 outline-slate-300 leading-5" },
41
+ },
42
+ methods: {
43
+ applyFilter: function() {
44
+ this.inputContent = this.inputContent.trim()
45
+ this.activeFilter = "(" + (this.inputContent ? this.inputContent.replace(/\n/,' ') : "*") +")"
46
+ this.$set(this.component.vars,this.outputVar,this.activeFilter)
47
+ },
48
+ resize() {
49
+ const { textarea } = this.$refs;
50
+ if(this.inputContent && ( textarea.textLength >= textarea.cols || this.inputContent.split("\n").length > 1) ) {
51
+ textarea.style.height = "auto";
52
+ textarea.style.height = (textarea.scrollHeight + 2) + 'px'; // Os 14px são do padding acrescentado
53
+ } else {
54
+ textarea.style.height = "40px";
55
+ }
56
+ }
57
+ }
58
+ }
59
+ </script>