@jfvilas/plugin-kwirth-log 0.12.7 → 0.13.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 (29) hide show
  1. package/README.md +67 -8
  2. package/dist/api/KwirthLogClient.esm.js +4 -56
  3. package/dist/api/KwirthLogClient.esm.js.map +1 -1
  4. package/dist/api/types.esm.js.map +1 -1
  5. package/dist/components/{EntityKwirthLogContent/EntityKwirthLogContent.esm.js → EntityKwirthLogContent.esm.js} +119 -50
  6. package/dist/components/EntityKwirthLogContent.esm.js.map +1 -0
  7. package/dist/components/Options.esm.js +20 -0
  8. package/dist/components/Options.esm.js.map +1 -0
  9. package/dist/index.d.ts +16 -6
  10. package/dist/index.esm.js +4 -0
  11. package/dist/index.esm.js.map +1 -1
  12. package/dist/plugin.esm.js +1 -1
  13. package/dist/plugin.esm.js.map +1 -1
  14. package/dist/routes.esm.js.map +1 -1
  15. package/package.json +1 -1
  16. package/dist/assets/kwirth-log-component-not-found.svg +0 -30
  17. package/dist/components/ClusterList/ClusterList.esm.js +0 -30
  18. package/dist/components/ClusterList/ClusterList.esm.js.map +0 -1
  19. package/dist/components/ComponentNotFound/ComponentNotFound.esm.js +0 -64
  20. package/dist/components/ComponentNotFound/ComponentNotFound.esm.js.map +0 -1
  21. package/dist/components/EntityKwirthLogContent/EntityKwirthLogContent.esm.js.map +0 -1
  22. package/dist/components/EntityKwirthLogContent/index.esm.js +0 -2
  23. package/dist/components/EntityKwirthLogContent/index.esm.js.map +0 -1
  24. package/dist/components/ObjectSelector/ObjectSelector.esm.js +0 -84
  25. package/dist/components/ObjectSelector/ObjectSelector.esm.js.map +0 -1
  26. package/dist/components/Options/Options.esm.js +0 -20
  27. package/dist/components/Options/Options.esm.js.map +0 -1
  28. package/dist/components/StatusLog/StatusLog.esm.js +0 -10
  29. package/dist/components/StatusLog/StatusLog.esm.js.map +0 -1
package/README.md CHANGED
@@ -28,7 +28,9 @@ Let's explain this by following a user working sequence:
28
28
  1. A Backstage user searchs for an entity in the Backstage.
29
29
  2. In the entity page there will be a new tab named 'KWIRTHLOG'.
30
30
  3. When the user clicks on KWIRTHLOG the frontend plugin sends a request to the backend Kiwrth plugin asking for logging information on all Kubernetes clusters available.
31
- 4. The Kwirth backend plugin sends requests to the Kwirth instances that are running inside all the clusters added to Backstage. These requests ask for the following: *'Tell me all the pods that are labeled with the kubernetes-id label and do correspond with the entity I'm looking for'*. In response to this query, each Kwirth instance answers with a list of pods and the namespaces where they are running.
31
+ 4. Next step is to identify the Kubernetes objects that match requested entity. As well as it occurs with other Backstage Kwirth plugins, Kwirth implements two strategies for getting the listo of kubernetes objects that match:
32
+ - Option 1. Your catalog-info contains an annotation of this type: **backstage.io/kubernetes-id**. In this case, the Backstage Kwirth backend plugin sends requests to the Kwirth instances that are running inside all the clusters added to Backstage. These requests ask for the following: *'Tell me all the pods that are labeled with the kubernetes-id label and do correspond with the entity I'm looking for'*. In response to this query, each Kwirth instance answers with a list of pods and the namespaces where they are running.
33
+ - Option 2. Your catalog-info contains an annotation of this type: **backstage.io/kubernetes-label-selector**. In this case, the Backstage Kwirth backend plugin sends requests to the Kwirth instances that are running inside all the clusters added to Backstage. These requests ask for the following: *'Tell me all the pods whose labels match with the kubernetes-label-selector label selector*. In response to this query, each Kwirth instance answers with a list of pods and the namespaces where they are running.
32
34
  5. The Kwirth backend plugin checks then the permissions of the connected user and prunes the pods list removing the ones that the user has not access to.
33
35
  6. With the final pod list, the backend plugin sends requests to the Kwirth instances on the clusters asking for API Keys specific for streaming pod logs.
34
36
  7. With all this information, the backend builds a unique response containing all the pods the user have access to, and all the API keys needed to access those logs.
@@ -41,8 +43,10 @@ Following table shows version compatibility between this Kwirth Backstage plugin
41
43
 
42
44
  | Plugin Kwirth version | Kwirth version |
43
45
  |-|-|
44
- |0.11.3|0.3.160|
46
+ |0.13.0|0.4.131|
47
+ |0.12.8|0.4.45|
45
48
  |0.12.5|0.4.20|
49
+ |0.11.3|0.3.160|
46
50
 
47
51
 
48
52
  ## Installation
@@ -75,7 +79,7 @@ For Kwirth plugin to be usable on the frontend, you must tailor your Entity Page
75
79
  ```
76
80
 
77
81
  Then, add a tab to your EntityPage (the 'if' is optional, you can keep the 'KwirthLog' tab always visible if you prefer to do it that way).
78
- ````jsx
82
+ ```jsx
79
83
  // Note: Add to any other Pages as well (e.g. defaultEntityPage or webSiteEntityPage, for example)
80
84
  const serviceEntityPage = (
81
85
  <EntityLayout>
@@ -87,15 +91,47 @@ For Kwirth plugin to be usable on the frontend, you must tailor your Entity Page
87
91
  )
88
92
  ```
89
93
 
90
- 2. Add `backstage.io/kubernetes-id` annotation to your `catalog-info.yaml` for the entities deployed to Kubernetes whose logs you want to be 'viewable' on Backstage. This is the same annotation that the Kubernetes core plugin uses, so, maybe you already have added it to your components.
94
+ You can setup some default *viewing* options on the `EntityKwirthLogContent` component, so, when the entity loads the default options will be set. These options are:
95
+ - `formStart`
96
+ - `showNames`
97
+ - `showTimestamp`
98
+ - `followLog`
99
+ - `wrapLines`
100
+ (The meaning of these properties are explained at the end of this document)
101
+
102
+ For example, you could setup your default log stream like this:
103
+ ```jsx
104
+ ...
105
+ <EntityLayout.Route if={isKwirthAvailable} path="/kwirthlog" title="KwirthLog">
106
+ <EntityKwirthLogContent enableRestart={false} fromStrat={true} showTimestamp={true} wrapLines={true} />
107
+ </EntityLayout.Route>
108
+ ...
109
+ ```
110
+
111
+ 2. Label your catalog-info according to one of these two startegies:
112
+
113
+ - **Strategy 1: one-to-one**. Add `backstage.io/kubernetes-id` annotation to your `catalog-info.yaml` for the entities deployed to Kubernetes you want to work with on Backstage. This is the same annotation that the Kubernetes core plugin uses, so, maybe you already have added it to your components. Exmaple:
114
+
115
+ ```yaml
116
+ metadata:
117
+ annotations:
118
+ backstage.io/kubernetes-id: entity001
119
+ ```
120
+
121
+ - **Strategy 2: use selectors**. Add `backstage.io/kubernetes-label-selector` annotation to your `catalog-info.yaml` for the entities you want to work with. This is the same annotation that the Kubernetes core plugin uses, so, maybe you already have added it to your components. The label selector value follows Kubernetes [label selector semantics](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). Example:
91
122
 
92
123
  ```yaml
93
124
  metadata:
94
125
  annotaations:
95
- backstage.io/kubernetes-id: entity-name
126
+ backstage.io/kubernetes-id: 'app=core,artifact=backend'
96
127
  ```
97
128
 
98
- 3. Add proper **labels** to your Kubernetes objects so Backstage can *link* forward and backward the Backstage entities with the Kubernetes objects. To do this, you need to add `labels` to your Kubernetes YAML objects (please, don't get confused: **annotations in Backstage YAML, labels in Kubernetes YAML**). This is an example of a typical Kubernetes deployment with the required label:
129
+ 3. Add proper **labels** to your Kubernetes objects so Backstage can *link* forward and backward the Backstage entities with the Kubernetes objects. To do this, you need to add `labels` to your Kubernetes YAML objects (please, don't get confused: **annotations in Backstage YAML, labels in Kubernetes YAML**).
130
+
131
+ - ***VERY IMPORTANT NOTE:*** If you opted for using label selectors **you have nothing new to add to your pods**.
132
+
133
+ - If you use labels (no label selectors), please note that the kubernetes-id label is **on the deployment** and on the **spec pod template** also. This is an example of a typical Kubernetes deployment with the required label:
134
+
99
135
 
100
136
  ```yaml
101
137
  apiVersion: apps/v1
@@ -121,8 +157,6 @@ For Kwirth plugin to be usable on the frontend, you must tailor your Entity Page
121
157
  ...
122
158
  ```
123
159
 
124
- Please note that the kubernetes-id label is **on the deployment** and on the **spec pod template** also.
125
-
126
160
  ## Ready, set, go!
127
161
  If you followed all these steps and our work is well done (not sure on this), you would see a 'KwirthLog' tab in your **Entity Page**, like this one:
128
162
 
@@ -150,6 +184,13 @@ Now you will see your log refreshing in real-time. If you selected more than one
150
184
 
151
185
  ![running](https://raw.githubusercontent.com/jfvilas/plugin-kwirth-log/master/images/kwirthlog-running.png)
152
186
 
187
+ ### Filtering
188
+ When the log stream starts you will see a filter text field you can use for filtering messages. The use is as follows:
189
+ - If you type something, the messages (or the pod names or the container names) are expected to match that text.
190
+ - If you select `Aa`, the text match will be performed taking **casing** into account.
191
+ - If you select `.*` then the text entered will be treated as a regular expression.
192
+ - You can select *casing* and *regex* if you want regexes to match taking casing into account.
193
+
153
194
  Feel free to open issues and ask for more features.
154
195
 
155
196
  ## Status information
@@ -166,3 +207,21 @@ This is how it feels:
166
207
 
167
208
  If you click on one of the status icons when theyr are enableds (coloured), you will see the detail of the status.
168
209
  ![status detail](https://raw.githubusercontent.com/jfvilas/plugin-kwirth-log/master/images/kwirthlog-status-detail.png)
210
+
211
+ ## Log stream options
212
+ The log streaming system implemented in KwirthLog has some configuration options whose meaning is explained below.
213
+
214
+ ### From start (`fromStart`)
215
+ If checked, the log streams will be retieved since pod start time. Please be careful with this option, it may impact your browser performance.
216
+
217
+ ### Show message time (`showTimestamp`)
218
+ Decide whether to show timestamps next to messages or not. Usually your application logs messages with timestamp, but it this doesn't occur you can add message time (as provided by Kubernetes) to your messages.
219
+
220
+ ### Show pod names (`showNames`)
221
+ Show pod names and container names next to messages. You may need to use this option if you are showing messages from different sources.
222
+
223
+ ### Follow log stream (`followLog`)
224
+ If you activate this option, the log stream wil move to the end wehn a new messages arrives (it's like a `tail -f`).
225
+
226
+ ### Wrap log lines (`wrapLines`)
227
+ Normally, KwirthLog will add hotizontal and vertical scrollers as needed in order to preserve original log lines, but you can activate this option to wrap lines and not to show horizontal scroller.
@@ -1,4 +1,4 @@
1
- import { getVersion, getResources, requestAccess } from '@jfvilas/plugin-kwirth-common';
1
+ import { getVersion, getInfo, getResources, requestAccess } from '@jfvilas/plugin-kwirth-common';
2
2
 
3
3
  class KwirthLogClient {
4
4
  discoveryApi;
@@ -12,70 +12,18 @@ class KwirthLogClient {
12
12
  * @param entity
13
13
  * @returns an array of clusters (with their correpsonding info) and a pod list for each, where the entity has been dicovered
14
14
  */
15
- // +++ test
16
15
  async getVersion() {
17
16
  return getVersion(this.discoveryApi, this.fetchApi);
18
17
  }
19
- // async getVersion(): Promise<string> {
20
- // try {
21
- // const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')
22
- // const targetUrl = `${baseUrl}/version`
23
- // const result = await this.fetchApi.fetch(targetUrl)
24
- // const data = await result.json()
25
- // if (!result.ok) {
26
- // throw new Error(`getVersion error: not ok`)
27
- // }
28
- // return data.version
29
- // }
30
- // catch (err) {
31
- // throw new Error(`getVersion error: ${err}`)
32
- // }
33
- // }
34
- // move to common
35
- // async getResources(entity:Entity): Promise<ClusterValidPods> {
36
- // try {
37
- // const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')
38
- // const targetUrl = `${baseUrl}/start`
39
- // var payload=JSON.stringify(entity)
40
- // const result = await this.fetchApi.fetch(targetUrl, {method:'POST', body:payload, headers:{'Content-Type':'application/json'}})
41
- // const data = await result.json() as ClusterValidPods
42
- // if (!result.ok) {
43
- // throw new Error(`getResources error: not ok`)
44
- // }
45
- // return data
46
- // }
47
- // catch (err) {
48
- // throw new Error(`getResources error: ${err}`)
49
- // }
50
- // }
18
+ async getInfo() {
19
+ return getInfo(this.discoveryApi, this.fetchApi);
20
+ }
51
21
  async getResources(entity) {
52
22
  return getResources(this.discoveryApi, this.fetchApi, entity);
53
23
  }
54
24
  async requestAccess(entity, channel, scopes) {
55
25
  return requestAccess(this.discoveryApi, this.fetchApi, entity, channel, scopes);
56
26
  }
57
- // async requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]> {
58
- // try {
59
- // const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')
60
- // var targetUrl:URL= new URL (`${baseUrl}/access`)
61
- // targetUrl.searchParams.append('scopes',scopes.join(','))
62
- // targetUrl.searchParams.append('channel',channel)
63
- // var payload=JSON.stringify(entity)
64
- // const result = await this.fetchApi.fetch(targetUrl, {method:'POST', body:payload, headers:{'Content-Type':'application/json'}})
65
- // const data = await result.json() as ClusterValidPods[]
66
- // // we reconstruct the 'Map' from string of arrays
67
- // for (var c of data) {
68
- // c.accessKeys = new Map(JSON.parse(((c as any).accessKeys)))
69
- // }
70
- // if (!result.ok) {
71
- // throw new Error(`requestAccess error: not ok`)
72
- // }
73
- // return data
74
- // }
75
- // catch (err) {
76
- // throw new Error(`requestAccess error: ${err}`)
77
- // }
78
- // }
79
27
  }
80
28
 
81
29
  export { KwirthLogClient };
@@ -1 +1 @@
1
- {"version":3,"file":"KwirthLogClient.esm.js","sources":["../../src/api/KwirthLogClient.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'\r\nimport { KwirthLogApi } from './types'\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { ClusterValidPods, getResources, getVersion, requestAccess } from '@jfvilas/plugin-kwirth-common'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\n\r\nexport interface KwirthLogClientOptions {\r\n discoveryApi: DiscoveryApi\r\n fetchApi: FetchApi\r\n}\r\n\r\nexport class KwirthLogClient implements KwirthLogApi {\r\n private readonly discoveryApi: DiscoveryApi\r\n private readonly fetchApi: FetchApi\r\n\r\n constructor(options: KwirthLogClientOptions) {\r\n this.discoveryApi = options.discoveryApi\r\n this.fetchApi = options.fetchApi\r\n }\r\n\r\n /**\r\n * \r\n * @param entity \r\n * @returns an array of clusters (with their correpsonding info) and a pod list for each, where the entity has been dicovered\r\n */\r\n // +++ test\r\n async getVersion() : Promise<string> {\r\n return getVersion(this.discoveryApi, this.fetchApi)\r\n }\r\n // async getVersion(): Promise<string> {\r\n // try {\r\n // const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')\r\n // const targetUrl = `${baseUrl}/version`\r\n\r\n // const result = await this.fetchApi.fetch(targetUrl)\r\n // const data = await result.json()\r\n\r\n // if (!result.ok) {\r\n // throw new Error(`getVersion error: not ok`)\r\n // }\r\n // return data.version\r\n // }\r\n // catch (err) {\r\n // throw new Error(`getVersion error: ${err}`)\r\n // }\r\n // }\r\n\r\n // move to common\r\n // async getResources(entity:Entity): Promise<ClusterValidPods> {\r\n // try {\r\n // const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')\r\n // const targetUrl = `${baseUrl}/start`\r\n\r\n // var payload=JSON.stringify(entity)\r\n // const result = await this.fetchApi.fetch(targetUrl, {method:'POST', body:payload, headers:{'Content-Type':'application/json'}})\r\n // const data = await result.json() as ClusterValidPods\r\n\r\n // if (!result.ok) {\r\n // throw new Error(`getResources error: not ok`)\r\n // }\r\n // return data\r\n // }\r\n // catch (err) {\r\n // throw new Error(`getResources error: ${err}`)\r\n // }\r\n // }\r\n async getResources(entity:Entity): Promise<ClusterValidPods> {\r\n return getResources(this.discoveryApi, this.fetchApi, entity)\r\n }\r\n\r\n async requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]> {\r\n return requestAccess(this.discoveryApi, this.fetchApi, entity, channel, scopes)\r\n }\r\n\r\n // async requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]> {\r\n // try {\r\n // const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')\r\n // var targetUrl:URL= new URL (`${baseUrl}/access`)\r\n // targetUrl.searchParams.append('scopes',scopes.join(','))\r\n // targetUrl.searchParams.append('channel',channel)\r\n\r\n // var payload=JSON.stringify(entity)\r\n // const result = await this.fetchApi.fetch(targetUrl, {method:'POST', body:payload, headers:{'Content-Type':'application/json'}})\r\n // const data = await result.json() as ClusterValidPods[]\r\n\r\n // // we reconstruct the 'Map' from string of arrays\r\n // for (var c of data) {\r\n // c.accessKeys = new Map(JSON.parse(((c as any).accessKeys)))\r\n // }\r\n // if (!result.ok) {\r\n // throw new Error(`requestAccess error: not ok`)\r\n // }\r\n // return data\r\n // }\r\n // catch (err) {\r\n // throw new Error(`requestAccess error: ${err}`)\r\n // }\r\n // }\r\n\r\n}\r\n"],"names":[],"mappings":";;AA0BO,MAAM,eAAwC,CAAA;AAAA,EAChC,YAAA;AAAA,EACA,QAAA;AAAA,EAEjB,YAAY,OAAiC,EAAA;AACzC,IAAA,IAAA,CAAK,eAAe,OAAQ,CAAA,YAAA;AAC5B,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AAAA;AAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAA+B,GAAA;AACjC,IAAA,OAAO,UAAW,CAAA,IAAA,CAAK,YAAc,EAAA,IAAA,CAAK,QAAQ,CAAA;AAAA;AACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsCA,MAAM,aAAa,MAA0C,EAAA;AACzD,IAAA,OAAO,YAAa,CAAA,IAAA,CAAK,YAAc,EAAA,IAAA,CAAK,UAAU,MAAM,CAAA;AAAA;AAChE,EAEA,MAAM,aAAA,CAAc,MAAe,EAAA,OAAA,EAAgB,MAA+D,EAAA;AAC9G,IAAA,OAAO,cAAc,IAAK,CAAA,YAAA,EAAc,KAAK,QAAU,EAAA,MAAA,EAAQ,SAAS,MAAM,CAAA;AAAA;AAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BJ;;;;"}
1
+ {"version":3,"file":"KwirthLogClient.esm.js","sources":["../../src/api/KwirthLogClient.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'\r\nimport { KwirthLogApi } from './types'\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { ClusterValidPods, getInfo, getResources, getVersion, IBackendInfo, requestAccess } from '@jfvilas/plugin-kwirth-common'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\n\r\nexport interface KwirthLogClientOptions {\r\n discoveryApi: DiscoveryApi\r\n fetchApi: FetchApi\r\n}\r\n\r\nexport class KwirthLogClient implements KwirthLogApi {\r\n private readonly discoveryApi: DiscoveryApi\r\n private readonly fetchApi: FetchApi\r\n\r\n constructor(options: KwirthLogClientOptions) {\r\n this.discoveryApi = options.discoveryApi\r\n this.fetchApi = options.fetchApi\r\n }\r\n\r\n /**\r\n * \r\n * @param entity \r\n * @returns an array of clusters (with their correpsonding info) and a pod list for each, where the entity has been dicovered\r\n */\r\n async getVersion() : Promise<string> {\r\n return getVersion(this.discoveryApi, this.fetchApi)\r\n }\r\n\r\n async getInfo() : Promise<IBackendInfo> {\r\n return getInfo(this.discoveryApi, this.fetchApi)\r\n }\r\n\r\n async getResources(entity:Entity): Promise<ClusterValidPods> {\r\n return getResources(this.discoveryApi, this.fetchApi, entity)\r\n }\r\n\r\n async requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]> {\r\n return requestAccess(this.discoveryApi, this.fetchApi, entity, channel, scopes)\r\n }\r\n\r\n}\r\n"],"names":[],"mappings":";;AA0BO,MAAM,eAAA,CAAwC;AAAA,EAChC,YAAA;AAAA,EACA,QAAA;AAAA,EAEjB,YAAY,OAAA,EAAiC;AACzC,IAAA,IAAA,CAAK,eAAe,OAAA,CAAQ,YAAA;AAC5B,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAA,GAA+B;AACjC,IAAA,OAAO,UAAA,CAAW,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,QAAQ,CAAA;AAAA,EACtD;AAAA,EAEA,MAAM,OAAA,GAAkC;AACpC,IAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,QAAQ,CAAA;AAAA,EACnD;AAAA,EAEA,MAAM,aAAa,MAAA,EAA0C;AACzD,IAAA,OAAO,YAAA,CAAa,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,UAAU,MAAM,CAAA;AAAA,EAChE;AAAA,EAEA,MAAM,aAAA,CAAc,MAAA,EAAe,OAAA,EAAgB,MAAA,EAA+D;AAC9G,IAAA,OAAO,cAAc,IAAA,CAAK,YAAA,EAAc,KAAK,QAAA,EAAU,MAAA,EAAQ,SAAS,MAAM,CAAA;AAAA,EAClF;AAEJ;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.esm.js","sources":["../../src/api/types.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { createApiRef } from '@backstage/core-plugin-api'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\nimport { ClusterValidPods } from '@jfvilas/plugin-kwirth-common'\r\n\r\nexport interface KwirthLogApi {\r\n getResources(entity:Entity): Promise<any>\r\n requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]>\r\n getVersion(): Promise<any>\r\n}\r\n\r\nexport const kwirthLogApiRef = createApiRef<KwirthLogApi>({\r\n id: 'plugin.kwirthlog.api',\r\n})\r\n"],"names":[],"mappings":";;AA0BO,MAAM,kBAAkB,YAA2B,CAAA;AAAA,EACxD,EAAI,EAAA;AACN,CAAC;;;;"}
1
+ {"version":3,"file":"types.esm.js","sources":["../../src/api/types.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { createApiRef } from '@backstage/core-plugin-api'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\nimport { ClusterValidPods } from '@jfvilas/plugin-kwirth-common'\r\n\r\nexport interface KwirthLogApi {\r\n getResources(entity:Entity): Promise<any>\r\n requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]>\r\n getVersion(): Promise<any>\r\n getInfo(): any\r\n}\r\n\r\nexport const kwirthLogApiRef = createApiRef<KwirthLogApi>({\r\n id: 'plugin.kwirthlog.api',\r\n})\r\n"],"names":[],"mappings":";;AA2BO,MAAM,kBAAkB,YAAA,CAA2B;AAAA,EACxD,EAAA,EAAI;AACN,CAAC;;;;"}
@@ -2,16 +2,13 @@ import React, { useState, useRef } from 'react';
2
2
  import useAsync from 'react-use/esm/useAsync';
3
3
  import { Progress, WarningPanel } from '@backstage/core-components';
4
4
  import { useApi, alertApiRef } from '@backstage/core-plugin-api';
5
- import { isKwirthAvailable, ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR } from '@jfvilas/plugin-kwirth-common';
5
+ import { isKwirthAvailable, ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR, getPodList, getContainerList } from '@jfvilas/plugin-kwirth-common';
6
6
  import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
7
- import { kwirthLogApiRef } from '../../api/types.esm.js';
7
+ import { kwirthLogApiRef } from '../api/types.esm.js';
8
8
  import { SignalMessageLevelEnum, InstanceConfigScopeEnum, InstanceMessageChannelEnum, InstanceMessageTypeEnum, OpsCommandEnum, accessKeySerialize, InstanceMessageFlowEnum, InstanceMessageActionEnum, InstanceConfigViewEnum, InstanceConfigObjectEnum } from '@jfvilas/kwirth-common';
9
- import { ComponentNotFound, ErrorType } from '../ComponentNotFound/ComponentNotFound.esm.js';
10
- import { ObjectSelector } from '../ObjectSelector/ObjectSelector.esm.js';
11
- import { Options } from '../Options/Options.esm.js';
12
- import { ClusterList } from '../ClusterList/ClusterList.esm.js';
13
- import { StatusLog } from '../StatusLog/StatusLog.esm.js';
14
- import { Box, Grid, Card, CardHeader, CardContent } from '@material-ui/core';
9
+ import { Options } from './Options.esm.js';
10
+ import { ComponentNotFound, ErrorType, ClusterList, KwirthNews, ObjectSelector, StatusLog } from '@jfvilas/plugin-kwirth-frontend';
11
+ import { Box, Grid, Card, CardHeader, TextField, InputAdornment, CardContent } from '@material-ui/core';
15
12
  import Divider from '@material-ui/core/Divider';
16
13
  import IconButton from '@material-ui/core/IconButton';
17
14
  import Typography from '@material-ui/core/Typography';
@@ -22,7 +19,7 @@ import InfoIcon from '@material-ui/icons/Info';
22
19
  import WarningIcon from '@material-ui/icons/Warning';
23
20
  import ErrorIcon from '@material-ui/icons/Error';
24
21
  import DownloadIcon from '@material-ui/icons/CloudDownload';
25
- import KwirthLogLogo from '../../assets/kwirthlog-logo.svg';
22
+ import KwirthLogLogo from '../assets/kwirthlog-logo.svg';
26
23
  import RefreshIcon from '@material-ui/icons/Refresh';
27
24
 
28
25
  const LOG_MAX_MESSAGES = 1e3;
@@ -30,7 +27,7 @@ const EntityKwirthLogContent = (props) => {
30
27
  const { entity } = useEntity();
31
28
  const kwirthLogApi = useApi(kwirthLogApiRef);
32
29
  const alertApi = useApi(alertApiRef);
33
- const [resources, setResources] = useState([]);
30
+ const [validClusters, setResources] = useState([]);
34
31
  const [selectedClusterName, setSelectedClusterName] = useState("");
35
32
  const [selectedNamespaces, setSelectedNamespaces] = useState([]);
36
33
  const [selectedPodNames, setSelectedPodNames] = useState([]);
@@ -43,20 +40,33 @@ const EntityKwirthLogContent = (props) => {
43
40
  const [statusMessages, setStatusMessages] = useState([]);
44
41
  const [websocket, setWebsocket] = useState();
45
42
  const [instance, setInstance] = useState();
46
- const kwirthLogOptionsRef = useRef({ timestamp: false, follow: true, fromStart: false });
43
+ const kwirthLogOptionsRef = useRef({
44
+ fromStart: props.fromStart !== void 0 ? props.fromStart : false,
45
+ showTimestamp: props.showTimestamp !== void 0 ? props.showTimestamp : false,
46
+ showNames: props.showNames !== void 0 ? props.showNames : true,
47
+ followLog: props.followLog !== void 0 ? props.followLog : true,
48
+ wrapLines: props.wrapLines !== void 0 ? props.wrapLines : false
49
+ });
47
50
  const [showStatusDialog, setShowStatusDialog] = useState(false);
48
51
  const [statusLevel, setStatusLevel] = useState(SignalMessageLevelEnum.INFO);
49
52
  const preRef = useRef(null);
50
53
  const lastRef = useRef(null);
51
54
  const [backendVersion, setBackendVersion] = useState("");
55
+ const [backendInfo, setBackendInfo] = useState();
52
56
  const { loading, error } = useAsync(async () => {
53
57
  if (backendVersion === "") setBackendVersion(await kwirthLogApi.getVersion());
58
+ if (!backendInfo) setBackendInfo(await kwirthLogApi.getInfo());
54
59
  let reqScopes = [InstanceConfigScopeEnum.VIEW];
55
60
  if (props.enableRestart) reqScopes.push(InstanceConfigScopeEnum.RESTART);
56
61
  let data = await kwirthLogApi.requestAccess(entity, InstanceMessageChannelEnum.LOG, reqScopes);
57
62
  setResources(data);
58
63
  });
59
64
  const buffer = useRef(/* @__PURE__ */ new Map());
65
+ const [filter, setFilter] = useState("");
66
+ const [filterCasing, setFilterCasing] = useState(false);
67
+ const [filterRegex, setFilterRegex] = useState(false);
68
+ const adornmentSelected = { margin: 0, borderWidth: 1, borderStyle: "solid", borderColor: "gray", paddingLeft: 3, paddingRight: 3, backgroundColor: "gray", cursor: "pointer", color: "white" };
69
+ const adornmentNotSelected = { margin: 0, borderWidth: 1, borderStyle: "solid", borderColor: "#f0f0f0", backgroundColor: "#f0f0f0", paddingLeft: 3, paddingRight: 3, cursor: "pointer" };
60
70
  const clickStart = (options) => {
61
71
  if (!paused.current) {
62
72
  setStarted(true);
@@ -80,21 +90,32 @@ const EntityKwirthLogContent = (props) => {
80
90
  paused.current = false;
81
91
  stopLogViewer();
82
92
  };
83
- const onSelectCluster = (name) => {
84
- if (name) {
85
- setSelectedClusterName(name);
86
- setMessages([{
87
- type: InstanceMessageTypeEnum.SIGNAL,
88
- text: "Select namespace in order to decide which pod logs to view.",
89
- namespace: "",
90
- pod: "",
91
- container: ""
92
- }]);
93
- setSelectedNamespaces([]);
93
+ const onSelectCluster = (clusterName) => {
94
+ if (started) onClickStop();
95
+ if (clusterName) {
96
+ setSelectedClusterName(clusterName);
94
97
  setSelectedPodNames([]);
95
98
  setSelectedContainerNames([]);
96
99
  setStatusMessages([]);
97
- onClickStop();
100
+ let cluster = validClusters.find((cluster2) => cluster2.name === clusterName);
101
+ if (cluster && cluster.pods) {
102
+ let validNamespaces = Array.from(new Set(cluster.pods.map((pod) => pod.namespace)));
103
+ if (validNamespaces.length === 1) {
104
+ setSelectedNamespaces(validNamespaces);
105
+ let podList = getPodList(cluster.pods, validNamespaces);
106
+ setSelectedPodNames(podList.map((pod) => pod.name));
107
+ setSelectedContainerNames(getContainerList(cluster.pods, validNamespaces, podList.map((pod) => pod.name)));
108
+ } else {
109
+ setMessages([{
110
+ type: InstanceMessageTypeEnum.SIGNAL,
111
+ text: "Select namespace in order to decide which pod logs to view.",
112
+ namespace: "",
113
+ pod: "",
114
+ container: ""
115
+ }]);
116
+ setSelectedNamespaces([]);
117
+ }
118
+ }
98
119
  }
99
120
  };
100
121
  const processLogMessage = (wsEvent) => {
@@ -130,7 +151,7 @@ const EntityKwirthLogContent = (props) => {
130
151
  while (prev.length > LOG_MAX_MESSAGES - 1) {
131
152
  prev.splice(0, 1);
132
153
  }
133
- if (kwirthLogOptionsRef.current.follow && lastRef.current) lastRef.current.scrollIntoView({ behavior: "instant", block: "start" });
154
+ if (kwirthLogOptionsRef.current.followLog && lastRef.current) lastRef.current.scrollIntoView({ behavior: "instant", block: "start" });
134
155
  return [...prev, logLine];
135
156
  });
136
157
  }
@@ -142,24 +163,28 @@ const EntityKwirthLogContent = (props) => {
142
163
  setInstance(instanceMessage.instance);
143
164
  else {
144
165
  let signalMessage = instanceMessage;
145
- alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
166
+ if (signalMessage.text) {
167
+ alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
168
+ }
146
169
  }
147
170
  } else {
148
171
  let signalMessage = instanceMessage;
149
- addMessage(signalMessage.level, signalMessage.text);
150
- switch (signalMessage.level) {
151
- case SignalMessageLevelEnum.INFO:
152
- alertApi.post({ message: signalMessage.text, severity: "info", display: "transient" });
153
- break;
154
- case SignalMessageLevelEnum.WARNING:
155
- alertApi.post({ message: signalMessage.text, severity: "warning", display: "transient" });
156
- break;
157
- case SignalMessageLevelEnum.ERROR:
158
- alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
159
- break;
160
- default:
161
- alertApi.post({ message: signalMessage.text, severity: "success", display: "transient" });
162
- break;
172
+ if (signalMessage.text) {
173
+ addMessage(signalMessage.level, signalMessage.text);
174
+ switch (signalMessage.level) {
175
+ case SignalMessageLevelEnum.INFO:
176
+ alertApi.post({ message: signalMessage.text, severity: "info", display: "transient" });
177
+ break;
178
+ case SignalMessageLevelEnum.WARNING:
179
+ alertApi.post({ message: signalMessage.text, severity: "warning", display: "transient" });
180
+ break;
181
+ case SignalMessageLevelEnum.ERROR:
182
+ alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
183
+ break;
184
+ default:
185
+ alertApi.post({ message: signalMessage.text, severity: "success", display: "transient" });
186
+ break;
187
+ }
163
188
  }
164
189
  }
165
190
  break;
@@ -203,12 +228,12 @@ const EntityKwirthLogContent = (props) => {
203
228
  }
204
229
  };
205
230
  const websocketOnOpen = (ws, options) => {
206
- let cluster = resources.find((cluster2) => cluster2.name === selectedClusterName);
231
+ let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
207
232
  if (!cluster) {
208
233
  addMessage(SignalMessageLevelEnum.ERROR, "No cluster selected");
209
234
  return;
210
235
  }
211
- let pods = cluster.data.filter((p2) => selectedNamespaces.includes(p2.namespace));
236
+ let pods = cluster.pods.filter((p2) => selectedNamespaces.includes(p2.namespace));
212
237
  if (!pods) {
213
238
  addMessage(SignalMessageLevelEnum.ERROR, "No pods found");
214
239
  return;
@@ -238,7 +263,7 @@ const EntityKwirthLogContent = (props) => {
238
263
  pod: selectedPodNames.map((p2) => p2).join(","),
239
264
  container: containers.join(","),
240
265
  data: {
241
- timestamp: options.timestamp,
266
+ timestamp: options.showTimestamp,
242
267
  previous: false,
243
268
  maxMessages: LOG_MAX_MESSAGES,
244
269
  fromStart: options.fromStart
@@ -252,7 +277,7 @@ const EntityKwirthLogContent = (props) => {
252
277
  }
253
278
  };
254
279
  const startLogViewer = (options) => {
255
- let cluster = resources.find((cluster2) => cluster2.name === selectedClusterName);
280
+ let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
256
281
  if (!cluster) {
257
282
  addMessage(SignalMessageLevelEnum.ERROR, "No cluster selected");
258
283
  return;
@@ -314,7 +339,7 @@ const EntityKwirthLogContent = (props) => {
314
339
  URL.revokeObjectURL(url);
315
340
  };
316
341
  const onClickRestart = () => {
317
- var cluster = resources.find((cluster2) => cluster2.name === selectedClusterName);
342
+ var cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
318
343
  if (!cluster) {
319
344
  addMessage(SignalMessageLevelEnum.ERROR, "No cluster selected");
320
345
  return;
@@ -328,7 +353,7 @@ const EntityKwirthLogContent = (props) => {
328
353
  addMessage(SignalMessageLevelEnum.ERROR, "No instance has been established");
329
354
  return;
330
355
  }
331
- let pods = cluster.data.filter((pod) => selectedNamespaces.includes(pod.namespace));
356
+ let pods = cluster.pods.filter((pod) => selectedNamespaces.includes(pod.namespace));
332
357
  for (let pod of pods) {
333
358
  let opsMessage = {
334
359
  msgtype: "opsmessage",
@@ -361,7 +386,7 @@ const EntityKwirthLogContent = (props) => {
361
386
  };
362
387
  const actionButtons = () => {
363
388
  let hasViewKey = false, hasRestartKey = false;
364
- let cluster = resources.find((cluster2) => cluster2.name === selectedClusterName);
389
+ let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
365
390
  if (cluster) {
366
391
  hasViewKey = Boolean(cluster.accessKeys.get(InstanceConfigScopeEnum.VIEW));
367
392
  hasRestartKey = Boolean(cluster.accessKeys.get(InstanceConfigScopeEnum.RESTART));
@@ -373,7 +398,10 @@ const EntityKwirthLogContent = (props) => {
373
398
  setShowStatusDialog(true);
374
399
  setStatusLevel(level);
375
400
  };
376
- return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, title)), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { marginTop: "-8px" } }, /* @__PURE__ */ React.createElement(IconButton, { title: "info", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO), onClick: () => show(SignalMessageLevelEnum.INFO) }, /* @__PURE__ */ React.createElement(InfoIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO) ? "blue" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "warning", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING), onClick: () => show(SignalMessageLevelEnum.WARNING), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(WarningIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING) ? "orange" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "error", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR), onClick: () => show(SignalMessageLevelEnum.ERROR), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR) ? "red" : "#BDBDBD" } }))));
401
+ const prepareText = (txt) => {
402
+ return txt ? txt.length > 25 ? txt.substring(0, 25) + "..." : txt : "N/A";
403
+ };
404
+ return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, prepareText(title))), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { marginTop: "-8px" } }, /* @__PURE__ */ React.createElement(IconButton, { title: "info", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO), onClick: () => show(SignalMessageLevelEnum.INFO) }, /* @__PURE__ */ React.createElement(InfoIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO) ? "blue" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "warning", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING), onClick: () => show(SignalMessageLevelEnum.WARNING), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(WarningIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING) ? "orange" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "error", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR), onClick: () => show(SignalMessageLevelEnum.ERROR), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR) ? "red" : "#BDBDBD" } }))));
377
405
  };
378
406
  const statusClear = (level) => {
379
407
  setStatusMessages(statusMessages.filter((m) => m.level !== level));
@@ -384,12 +412,40 @@ const EntityKwirthLogContent = (props) => {
384
412
  setSelectedPodNames(podNames);
385
413
  setSelectedContainerNames(containerNames);
386
414
  };
415
+ const onChangeFilter = (event) => {
416
+ setFilter(event.target?.value);
417
+ };
387
418
  const formatMessage = (logLine) => {
388
419
  if (!logLine.pod) {
389
420
  return /* @__PURE__ */ React.createElement(React.Fragment, null, logLine.text + "\n");
390
421
  }
422
+ if (filter !== "") {
423
+ if (filterCasing) {
424
+ if (filterRegex) {
425
+ try {
426
+ const regex = new RegExp(filter);
427
+ if (!regex.test(logLine.text) && !regex.test(logLine.pod) && !regex.test(logLine.container)) return /* @__PURE__ */ React.createElement(React.Fragment, null);
428
+ } catch {
429
+ return /* @__PURE__ */ React.createElement(React.Fragment, null);
430
+ }
431
+ } else {
432
+ if (!logLine.text.includes(filter) && !logLine.pod.includes(filter) && !logLine.container.includes(filter)) return /* @__PURE__ */ React.createElement(React.Fragment, null);
433
+ }
434
+ } else {
435
+ if (filterRegex) {
436
+ try {
437
+ const regex = new RegExp(filter.toLocaleLowerCase());
438
+ if (!regex.test(logLine.text.toLocaleLowerCase()) && !regex.test(logLine.pod.toLocaleLowerCase()) && !regex.test(logLine.container.toLocaleLowerCase())) return /* @__PURE__ */ React.createElement(React.Fragment, null);
439
+ } catch {
440
+ return /* @__PURE__ */ React.createElement(React.Fragment, null);
441
+ }
442
+ } else {
443
+ if (!logLine.text.toLocaleLowerCase().includes(filter.toLowerCase()) && !logLine.pod.toLocaleLowerCase().includes(filter.toLocaleLowerCase()) && !logLine.container.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) return /* @__PURE__ */ React.createElement(React.Fragment, null);
444
+ }
445
+ }
446
+ }
391
447
  let podPrefix = /* @__PURE__ */ React.createElement(React.Fragment, null);
392
- if (selectedPodNames.length !== 1) {
448
+ if (selectedPodNames.length !== 1 || kwirthLogOptionsRef.current.showNames) {
393
449
  podPrefix = /* @__PURE__ */ React.createElement("span", { style: { color: "green" } }, logLine.pod + " ");
394
450
  }
395
451
  let containerPrefix = /* @__PURE__ */ React.createElement(React.Fragment, null);
@@ -398,14 +454,27 @@ const EntityKwirthLogContent = (props) => {
398
454
  }
399
455
  return /* @__PURE__ */ React.createElement(React.Fragment, null, podPrefix, containerPrefix, logLine.text + "\n");
400
456
  };
401
- return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), !isKwirthAvailable(entity) && !loading && error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "An error has ocurred while obtaining data from kuebernetes clusters.", message: error?.message }), !isKwirthAvailable(entity) && !loading && /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { readMoreUrl: "https://github.com/jfvilas/plugin-kwirth-log", annotation: [ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR] }), isKwirthAvailable(entity) && !loading && resources && resources.length === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_CLUSTERS, entity }), isKwirthAvailable(entity) && !loading && resources && resources.length > 0 && resources.reduce((sum, cluster) => sum + cluster.data.length, 0) === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_PODS, entity }), isKwirthAvailable(entity) && !loading && resources && resources.length > 0 && resources.reduce((sum, cluster) => sum + cluster.data.length, 0) > 0 && /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", height: "70vh" } }, /* @__PURE__ */ React.createElement(Box, { sx: { width: "200px" } }, /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(ClusterList, { resources, selectedClusterName, onSelect: onSelectCluster }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(Options, { options: kwirthLogOptionsRef.current, onChange: onChangeLogConfig, disabled: selectedContainerNames.length === 0 || started || paused.current }))))), /* @__PURE__ */ React.createElement(Box, { sx: { flexGrow: 1, flex: 1, overflow: "hidden", p: 1, maxWidth: "85%" } }, !selectedClusterName && /* @__PURE__ */ React.createElement("img", { src: KwirthLogLogo, alt: "No cluster selected", style: { left: "40%", marginTop: "10%", width: "20%", position: "relative" } }), selectedClusterName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { style: { marginTop: -8, height: "100%", display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(
457
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), !isKwirthAvailable(entity) && !loading && error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "An error has ocurred while obtaining data from kuebernetes clusters.", message: error?.message }), !isKwirthAvailable(entity) && !loading && /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { readMoreUrl: "https://github.com/jfvilas/plugin-kwirth-log", annotation: [ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR] }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_CLUSTERS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster) => sum + cluster.pods.length, 0) === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_PODS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster) => sum + cluster.pods.length, 0) > 0 && /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", height: "70vh" } }, /* @__PURE__ */ React.createElement(Box, { sx: { width: "200px", maxWidth: "200px" } }, /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(ClusterList, { resources: validClusters, selectedClusterName, onSelect: onSelectCluster }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(Options, { options: kwirthLogOptionsRef.current, onChange: onChangeLogConfig, disabled: selectedContainerNames.length === 0 || started || paused.current }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(KwirthNews, { latestVersions: backendInfo, backendVersion }))))), /* @__PURE__ */ React.createElement(Box, { sx: { flexGrow: 1, flex: 1, overflow: "hidden", p: 1, marginLeft: "8px" } }, !selectedClusterName && /* @__PURE__ */ React.createElement("img", { src: KwirthLogLogo, alt: "No cluster selected", style: { left: "40%", marginTop: "10%", width: "20%", position: "relative" } }), selectedClusterName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { style: { marginTop: -8, height: "100%", display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(
402
458
  CardHeader,
403
459
  {
404
460
  title: statusButtons(selectedClusterName),
405
461
  style: { marginTop: -4, marginBottom: 4, flexShrink: 0 },
406
462
  action: actionButtons()
407
463
  }
408
- ), /* @__PURE__ */ React.createElement(Typography, { style: { marginLeft: 16, marginBottom: 4 } }, /* @__PURE__ */ React.createElement(ObjectSelector, { cluster: resources.find((cluster) => cluster.name === selectedClusterName), onSelect: onSelectObject, disabled: selectedClusterName === "" || started || paused.current, selectedNamespaces, selectedPodNames, selectedContainerNames })), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, { style: { flexGrow: 1, overflow: "hidden", display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(Box, { style: { overflowY: "auto", overflowX: "auto", width: "100%", flexGrow: 1 } }, /* @__PURE__ */ React.createElement("pre", { ref: preRef }, messages.map((m) => formatMessage(m))), /* @__PURE__ */ React.createElement("span", { ref: lastRef }))))))), showStatusDialog && /* @__PURE__ */ React.createElement(StatusLog, { level: statusLevel, onClose: () => setShowStatusDialog(false), statusMessages, onClear: statusClear }));
464
+ ), /* @__PURE__ */ React.createElement(Grid, { container: true, style: { alignItems: "end" } }, /* @__PURE__ */ React.createElement(Grid, { item: true, style: { width: "66%" } }, /* @__PURE__ */ React.createElement(Typography, { style: { marginLeft: 14 } }, /* @__PURE__ */ React.createElement(ObjectSelector, { cluster: validClusters.find((cluster) => cluster.name === selectedClusterName), onSelect: onSelectObject, disabled: selectedClusterName === "" || started || paused.current, selectedNamespaces, selectedPodNames, selectedContainerNames, scope: InstanceConfigScopeEnum.VIEW }))), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { width: "33%", marginLeft: 0 } }, /* @__PURE__ */ React.createElement(
465
+ TextField,
466
+ {
467
+ value: filter,
468
+ onChange: onChangeFilter,
469
+ label: "Filter",
470
+ fullWidth: true,
471
+ style: { marginBottom: 6, marginLeft: 0 },
472
+ disabled: !started,
473
+ InputProps: {
474
+ endAdornment: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputAdornment, { position: "start", onClick: () => started && setFilterRegex(!filterRegex), style: { margin: 0 } }, /* @__PURE__ */ React.createElement(Typography, { style: filterRegex ? adornmentSelected : adornmentNotSelected }, ".*")), /* @__PURE__ */ React.createElement(InputAdornment, { position: "start", onClick: () => started && setFilterCasing(!filterCasing), style: { margin: 0, marginLeft: 1 } }, /* @__PURE__ */ React.createElement(Typography, { style: filterCasing ? adornmentSelected : adornmentNotSelected }, "Aa")))
475
+ }
476
+ }
477
+ ))), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, { style: { flexGrow: 1, overflow: "hidden", display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(Box, { style: { overflowY: "auto", width: "100%", flexGrow: 1 } }, /* @__PURE__ */ React.createElement("pre", { ref: preRef, style: { overflowX: kwirthLogOptionsRef.current.wrapLines ? "hidden" : "auto", whiteSpace: kwirthLogOptionsRef.current.wrapLines ? "pre-wrap" : "pre", wordBreak: kwirthLogOptionsRef.current.wrapLines ? "break-word" : "normal" } }, messages.map((m) => formatMessage(m))), /* @__PURE__ */ React.createElement("span", { ref: lastRef }))))))), showStatusDialog && /* @__PURE__ */ React.createElement(StatusLog, { level: statusLevel, onClose: () => setShowStatusDialog(false), statusMessages, onClear: statusClear }));
409
478
  };
410
479
 
411
480
  export { EntityKwirthLogContent };