From d5495dbf058b05d61b7f5174eec0b1603d2d0e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Fri, 27 Aug 2021 18:04:27 +0200 Subject: [PATCH 001/226] Add log viewer widget Co-authored-by: clmrv --- .../com/cognifide/cogboard/widget/WidgetIndex.kt | 4 +++- .../cogboard/widget/type/LogViewerWidget.kt | 15 +++++++++++++++ cogboard-webapp/src/components/widgets/index.js | 9 +++++++++ .../widgets/types/LogViewerWidget/index.js | 11 +++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt index 70e8e7992..e057a29be 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt @@ -20,6 +20,7 @@ import com.cognifide.cogboard.widget.type.WorldClockWidget import com.cognifide.cogboard.widget.type.randompicker.RandomPickerWidget import com.cognifide.cogboard.widget.type.sonarqube.SonarQubeWidget import com.cognifide.cogboard.widget.type.zabbix.ZabbixWidget +import com.cognifide.cogboard.widget.type.LogViewerWidget import io.vertx.core.Vertx import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject @@ -48,7 +49,8 @@ class WidgetIndex { "Jira Buckets" to JiraBucketsWidget::class.java, "Service Check" to ServiceCheckWidget::class.java, "SonarQube" to SonarQubeWidget::class.java, - "White Space" to WhiteSpaceWidget::class.java + "White Space" to WhiteSpaceWidget::class.java, + "Log Viewer" to LogViewerWidget::class.java, ) fun availableWidgets(): JsonArray { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt new file mode 100644 index 000000000..9a3b2d93f --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -0,0 +1,15 @@ +package com.cognifide.cogboard.widget.type + +import com.cognifide.cogboard.config.service.BoardsConfigService +import com.cognifide.cogboard.widget.BaseWidget +import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject + +class LogViewerWidget( + vertx: Vertx, + config: JsonObject, + serv: BoardsConfigService +) : BaseWidget(vertx, config, serv) { + + override fun updateState() { } +} diff --git a/cogboard-webapp/src/components/widgets/index.js b/cogboard-webapp/src/components/widgets/index.js index c432b5217..04dc99860 100644 --- a/cogboard-webapp/src/components/widgets/index.js +++ b/cogboard-webapp/src/components/widgets/index.js @@ -15,6 +15,7 @@ import AemBundleInfoWidget from './types/AemBundleInfoWidget'; import ZabbixWidget from './types/ZabbixWidget'; import LinkListWidget from './types/LinkListWidget'; import ToDoListWidget from './types/ToDoListWidget'; +import LogViewerWidget from './types/LogViewerWidget'; const widgetTypes = { WhiteSpaceWidget: { @@ -200,6 +201,14 @@ const widgetTypes = { component: LinkListWidget, dialogFields: ['LinkListItems'], initialStatus: 'NONE' + }, + LogViewerWidget: { + component: LogViewerWidget, + dialogFields: ['EndpointField', 'SchedulePeriod'], + validationConstraints: { + SchedulePeriod: { min: 3 } + }, + initialStatus: 'NONE' } }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js new file mode 100644 index 000000000..8d9e09285 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -0,0 +1,11 @@ +import React from 'react'; + +const LogViewerWidget = () => { + return <>TODO; +}; + +LogViewerWidget.propTypes = {}; + +LogViewerWidget.defaultProps = {}; + +export default LogViewerWidget; From 72c63c18ebc7e184f4fb8d7ab0349438c0b98af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 8 Sep 2021 19:29:46 +0200 Subject: [PATCH 002/226] Add more settings to logs widget Co-authored-by: clmrv --- .../cogboard/widget/type/LogViewerWidget.kt | 15 +++++- .../components/widgets/dialogFields/index.js | 45 +++++++++++++++++ .../src/components/widgets/index.js | 14 +++++- .../widgets/types/LogViewerWidget/index.js | 48 +++++++++++++++++-- 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 9a3b2d93f..efadc4a68 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -11,5 +11,18 @@ class LogViewerWidget( serv: BoardsConfigService ) : BaseWidget(vertx, config, serv) { - override fun updateState() { } + override fun updateState() { + updateStateByCopingPropsToContent(PROPS) + } + + companion object { + val PROPS = setOf( + "endpoint", + "schedulePeriod", + "path", + "logLinesField", + "logFileSizeField", + "logRecordExpirationField" + ) + } } diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index f1c3ba7c2..2ec23c533 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -564,6 +564,51 @@ const dialogFields = { name: 'linkListItems', initialValue: [], validator: () => array().ensure() + }, + LogLinesField: { + component: NumberInput, + name: 'logLinesField', + label: 'Number of lines to return', + initialValue: 1000, + min: 1, + step: 1, + pattern: /\d*/, + valueUpdater: transformMinValue(), + validator: ({ min, max }) => + number() + .min(min, vm.NUMBER_MIN('Lines', min)) + .max(max, vm.NUMBER_MAX('Lines', max)) + .required(vm.FIELD_REQUIRED()) + }, + LogFileSizeField: { + component: NumberInput, + name: 'logFileSizeField', + label: 'Log file size limit [MB]', + initialValue: 50, + min: 1, + step: 1, + pattern: /\d*/, + valueUpdater: transformMinValue(), + validator: ({ min, max }) => + number() + .min(min, vm.NUMBER_MIN('File size [MB]', min)) + .max(max, vm.NUMBER_MAX('File size [MB]', max)) + .required(vm.FIELD_REQUIRED()) + }, + LogRecordExpirationField: { + component: NumberInput, + name: 'logRecordExpirationField', + label: 'Log record expiration period [days]', + initialValue: 5, + min: 1, + step: 1, + pattern: /\d*/, + valueUpdater: transformMinValue(), + validator: ({ min, max }) => + number() + .min(min, vm.NUMBER_MIN('Days', min)) + .max(max, vm.NUMBER_MAX('Days', max)) + .required(vm.FIELD_REQUIRED()) } }; diff --git a/cogboard-webapp/src/components/widgets/index.js b/cogboard-webapp/src/components/widgets/index.js index 04dc99860..0230f2df8 100644 --- a/cogboard-webapp/src/components/widgets/index.js +++ b/cogboard-webapp/src/components/widgets/index.js @@ -204,9 +204,19 @@ const widgetTypes = { }, LogViewerWidget: { component: LogViewerWidget, - dialogFields: ['EndpointField', 'SchedulePeriod'], + dialogFields: [ + 'EndpointField', + 'SchedulePeriod', + 'Path', + 'LogLinesField', + 'LogFileSizeField', + 'LogRecordExpirationField' + ], validationConstraints: { - SchedulePeriod: { min: 3 } + SchedulePeriod: { min: 3 }, + LogLinesField: { min: 1, max: 10_000_000 }, + LogFileSizeField: { min: 1, max: 1024 }, + LogRecordExpirationField: { min: 1, max: 1000 } }, initialStatus: 'NONE' } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 8d9e09285..cff865518 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,11 +1,51 @@ import React from 'react'; +import { number, string } from 'prop-types'; -const LogViewerWidget = () => { - return <>TODO; +const LogViewerWidget = ({ + id, + endpoint, + schedulePeriod, + path, + logLinesField, + logFileSizeField, + logRecordExpirationField +}) => { + return ( + <> + id: {id} +
+ endpoint: {endpoint} +
+ schedulePeriod: {schedulePeriod} +
+ logFileSizeField: {logFileSizeField} +
+ logLinesField: {logLinesField} +
+ logRecordExpirationField: {logRecordExpirationField} +
+ path: {path} +
+ + ); }; -LogViewerWidget.propTypes = {}; +LogViewerWidget.propTypes = { + endpoint: string, + schedulePeriod: number, + path: string, + logLinesField: number, + logFileSizeField: number, + logRecordExpirationField: number +}; -LogViewerWidget.defaultProps = {}; +LogViewerWidget.defaultProps = { + endpoint: '', + schedulePeriod: 60, + path: '', + logLinesField: 1000, + logFileSizeField: 50, + logRecordExpirationField: 5 +}; export default LogViewerWidget; From 0f117cab9ceff9b803074dfd75bb73e64d71cba3 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sun, 12 Sep 2021 22:33:02 +0200 Subject: [PATCH 003/226] Added SSH Client for new type of connection --- cogboard-app/build.gradle.kts | 1 + .../cognifide/cogboard/CogboardConstants.kt | 6 + .../com/cognifide/cogboard/ssh/SSHClient.kt | 134 ++++++++++++++++++ .../cogboard/ssh/auth/AuthenticationType.kt | 7 + 4 files changed, 148 insertions(+) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt diff --git a/cogboard-app/build.gradle.kts b/cogboard-app/build.gradle.kts index 6cb0de1a2..f1ce38a03 100644 --- a/cogboard-app/build.gradle.kts +++ b/cogboard-app/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { } implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.0") implementation(kotlin("stdlib-jdk8")) + implementation("com.jcraft:jsch:0.1.55") testImplementation("org.assertj:assertj-core:3.12.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.4.2") diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index b781386a9..320b5de80 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -39,6 +39,11 @@ class CogboardConstants { const val SCHEDULE_PERIOD_DEFAULT = 120L // 120 seconds const val SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds const val URL = "url" + const val SSH_HOST = "sshAddress" + const val SSH_COMMAND = "sshCommand" + const val LOG_LINES = "logLines" + const val LOG_FILE_PATH = "logFilePath" + const val SSH_KEY = "sshKey" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" const val USER = "user" @@ -107,6 +112,7 @@ class CogboardConstants { const val HTTP_POST = "cogboard.httpclient.post" const val HTTP_PUT = "cogboard.httpclient.put" const val HTTP_DELETE = "cogboard.httpclient.delete" + const val SSH_COMMAND = "cogboard.sshclient." } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt new file mode 100644 index 000000000..62156b6a0 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -0,0 +1,134 @@ +package com.cognifide.cogboard.ssh + +import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.ssh.auth.AuthenticationType +import com.jcraft.jsch.Channel +import com.jcraft.jsch.JSch +import com.jcraft.jsch.Session +import io.reactivex.functions.Consumer +import io.vertx.core.AbstractVerticle +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.Json +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject +import java.io.InputStream +import java.io.OutputStream +import kotlin.properties.Delegates + +class SSHClient : AbstractVerticle() { + private lateinit var session: Session + private lateinit var jsch: JSch + private lateinit var channel: Channel + private lateinit var sshInputStream: InputStream + private lateinit var sshOutputStream: OutputStream + private lateinit var consumer: MessageConsumer + override fun start() { + registerSSHCommand() + super.start() + } + + override fun stop() { + consumer.unregister() + super.stop() + } + + private fun registerSSHCommand() { + consumer = vertx.eventBus().consumer(CogboardConstants.Event.SSH_COMMAND).handler { message -> + message.body()?.let { + connect(it) + } + } + } + + private fun connect(config: JsonObject) { + val user = config.getString(CogboardConstants.Props.USER) ?: "" + val pass = config.getString(CogboardConstants.Props.PASSWORD) ?: "" + val token = config.getString(CogboardConstants.Props.TOKEN) ?: "" + val key = config.getString(CogboardConstants.Props.SSH_KEY) ?: "" + val host = config.getString(CogboardConstants.Props.SSH_HOST) ?: "" + + val authTypesString = config.getString(CogboardConstants.Props.AUTHENTICATION_TYPES) + + val authTypes = if (authTypesString != null) Json.decodeValue(authTypesString) + else JsonArray() + + val authenticationType = getAuthenticationType(authTypes as JsonArray, user, token, pass, key) + + jsch = JSch() + + when (authenticationType) { + AuthenticationType.BASIC -> { + session = jsch.getSession(user, host) + session.setPassword(pass) + } + AuthenticationType.TOKEN -> { + session = jsch.getSession(user, host) + session.setPassword(token) + } + AuthenticationType.SSH_KEY -> { + jsch.addIdentity(key) + session = jsch.getSession(user, host) + } + } + + channel = session.openChannel("shell") + + sshInputStream = channel.inputStream + sshOutputStream = channel.outputStream + + channel.connect(5000) + + if (channel.isConnected) { + executeCommandAndSendResult(config) + } + } + + private fun executeCommandAndSendResult(config: JsonObject) { + val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) + val logLines = config.getString(CogboardConstants.Props.LOG_LINES) ?: "0" + val logFilePath = config.getString(CogboardConstants.Props.LOG_FILE_PATH) ?: "" + + val command = "cat $logFilePath | tail -$logLines" + sshOutputStream.write(command.toByteArray()) + val responseBuffer = Buffer.buffer() + Delegates.observable(sshInputStream.available()) { _, _, newValue -> + if (newValue > 0) { + responseBuffer.appendBytes(sshInputStream.readAllBytes()) + } + + vertx.eventBus().send(eventBusAddress, responseBuffer) + channel.disconnect() + session.disconnect() + } + } + + private fun getAuthenticationType( + authenticationTypes: JsonArray, + user: String, + token: String, + pass: String, + key: String + ): AuthenticationType { + + return authenticationTypes.stream() + .map { AuthenticationType.valueOf(it.toString()) } + .filter { hasAuthTypeCorrectCredentials(it, user, token, pass, key) } + .findFirst() + .orElse(AuthenticationType.BASIC) + } + + private fun hasAuthTypeCorrectCredentials( + authType: AuthenticationType, + username: String, + token: String, + pass: String, + key: String + ): Boolean { + return when { + authType == AuthenticationType.TOKEN && username.isNotBlank() && token.isNotBlank() -> true + authType == AuthenticationType.SSH_KEY && key.isNotBlank() -> true + else -> authType == AuthenticationType.BASIC && username.isNotBlank() && pass.isNotBlank() + } + } +} \ No newline at end of file diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt new file mode 100644 index 000000000..d8645437d --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt @@ -0,0 +1,7 @@ +package com.cognifide.cogboard.ssh.auth + +enum class AuthenticationType { + BASIC, + TOKEN, + SSH_KEY +} \ No newline at end of file From 70a0b276328ffa38fcfb24e585bc4595b06f53a8 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sun, 12 Sep 2021 22:34:07 +0200 Subject: [PATCH 004/226] Added SSH Receiver interface for widgets to use SSH connection --- .../com/cognifide/cogboard/ssh/SSHReceiver.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt new file mode 100644 index 000000000..160211926 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt @@ -0,0 +1,42 @@ +package com.cognifide.cogboard.ssh + +import com.cognifide.cogboard.CogboardConstants.* +import io.vertx.core.Handler +import io.vertx.core.Vertx +import io.vertx.core.eventbus.Message +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.JsonObject +import java.nio.Buffer + +interface SSHReceiver { + val vertx: Vertx + val user: String + val password: String + val sshKey: String + val host: String + val logPath: String + val logLines: Int + val handler: Handler> + var consumer: MessageConsumer? + + fun registerForSSH(eventBusAddress: String) { + consumer = vertx.eventBus().consumer(eventBusAddress, handler) + } + + fun unregisterFromSSH() { + consumer?.let { + it.unregister() + } + } + + fun sendRequestForLogs(config: JsonObject) { + config.getString(Props.USER) ?: config.put(Props.USER, user) + config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, password) + config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, sshKey) + config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, host) + config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, logPath) + config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, logLines.toString()) + + vertx.eventBus().send(Event.SSH_COMMAND, config) + } +} \ No newline at end of file From a28f7b34e440b3b2da0879a8dd423a64335088a7 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 15 Sep 2021 10:33:16 +0200 Subject: [PATCH 005/226] Remove UI placeholder #374 --- .../widgets/types/LogViewerWidget/index.js | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index cff865518..382a16c1e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -10,24 +10,7 @@ const LogViewerWidget = ({ logFileSizeField, logRecordExpirationField }) => { - return ( - <> - id: {id} -
- endpoint: {endpoint} -
- schedulePeriod: {schedulePeriod} -
- logFileSizeField: {logFileSizeField} -
- logLinesField: {logLinesField} -
- logRecordExpirationField: {logRecordExpirationField} -
- path: {path} -
- - ); + return <>; }; LogViewerWidget.propTypes = { From 297b33e8e4cf593cf396fe32d480c8a1ac86176a Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Fri, 17 Sep 2021 00:52:58 +0200 Subject: [PATCH 006/226] Changed SSHReceiver to SSHWidget extending AsyncWidget --- .../cognifide/cogboard/CogboardConstants.kt | 4 +- .../com/cognifide/cogboard/ssh/SSHClient.kt | 112 +++++++++++------- .../com/cognifide/cogboard/ssh/SSHReceiver.kt | 42 ------- .../cogboard/ssh/auth/AuthenticationType.kt | 2 +- .../cognifide/cogboard/widget/AsyncWidget.kt | 2 +- .../cognifide/cogboard/widget/SSHWidget.kt | 49 ++++++++ knotx/conf/cogboard.conf | 1 + 7 files changed, 124 insertions(+), 88 deletions(-) delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index 320b5de80..5aa18c4ca 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -38,9 +38,9 @@ class CogboardConstants { const val SCHEDULE_PERIOD = "schedulePeriod" const val SCHEDULE_PERIOD_DEFAULT = 120L // 120 seconds const val SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds + const val SSH_TIMEOUT = 5000 // 5000ms -> 5s const val URL = "url" const val SSH_HOST = "sshAddress" - const val SSH_COMMAND = "sshCommand" const val LOG_LINES = "logLines" const val LOG_FILE_PATH = "logFilePath" const val SSH_KEY = "sshKey" @@ -112,7 +112,7 @@ class CogboardConstants { const val HTTP_POST = "cogboard.httpclient.post" const val HTTP_PUT = "cogboard.httpclient.put" const val HTTP_DELETE = "cogboard.httpclient.delete" - const val SSH_COMMAND = "cogboard.sshclient." + const val SSH_COMMAND = "cogboard.sshclient.ssh" } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 62156b6a0..94321d93b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -2,10 +2,9 @@ package com.cognifide.cogboard.ssh import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.ssh.auth.AuthenticationType -import com.jcraft.jsch.Channel +import com.jcraft.jsch.ChannelExec import com.jcraft.jsch.JSch import com.jcraft.jsch.Session -import io.reactivex.functions.Consumer import io.vertx.core.AbstractVerticle import io.vertx.core.buffer.Buffer import io.vertx.core.eventbus.MessageConsumer @@ -13,15 +12,12 @@ import io.vertx.core.json.Json import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject import java.io.InputStream -import java.io.OutputStream -import kotlin.properties.Delegates class SSHClient : AbstractVerticle() { private lateinit var session: Session private lateinit var jsch: JSch - private lateinit var channel: Channel + private lateinit var channel: ChannelExec private lateinit var sshInputStream: InputStream - private lateinit var sshOutputStream: OutputStream private lateinit var consumer: MessageConsumer override fun start() { registerSSHCommand() @@ -34,14 +30,16 @@ class SSHClient : AbstractVerticle() { } private fun registerSSHCommand() { - consumer = vertx.eventBus().consumer(CogboardConstants.Event.SSH_COMMAND).handler { message -> - message.body()?.let { - connect(it) - } - } + consumer = vertx.eventBus() + .consumer(CogboardConstants.Event.SSH_COMMAND) + .handler { message -> + message.body()?.let { + connect(it) + } + } } - private fun connect(config: JsonObject) { + fun connect(config: JsonObject) { val user = config.getString(CogboardConstants.Props.USER) ?: "" val pass = config.getString(CogboardConstants.Props.PASSWORD) ?: "" val token = config.getString(CogboardConstants.Props.TOKEN) ?: "" @@ -55,60 +53,90 @@ class SSHClient : AbstractVerticle() { val authenticationType = getAuthenticationType(authTypes as JsonArray, user, token, pass, key) + val securityString: String = getAuthenticationString(authenticationType, pass, token, key) + + createSSHChannel(authenticationType, host, user, securityString, createCommand(config)) + + if (channel.isConnected) { + executeCommandAndSendResult(config) + } + } + + private fun createCommand(config: JsonObject): String { + val logLines = config.getString(CogboardConstants.Props.LOG_LINES) ?: "0" + val logFilePath = config.getString(CogboardConstants.Props.LOG_FILE_PATH) ?: "" + + return "cat $logFilePath | tail -$logLines" + } + + private fun createSSHChannel( + authenticationType: AuthenticationType, + host: String, + user: String, + securityString: String, // password / token / sshKey + command: String + ) { jsch = JSch() when (authenticationType) { AuthenticationType.BASIC -> { session = jsch.getSession(user, host) - session.setPassword(pass) + session.setPassword(securityString) } AuthenticationType.TOKEN -> { session = jsch.getSession(user, host) - session.setPassword(token) + session.setPassword(securityString) } AuthenticationType.SSH_KEY -> { - jsch.addIdentity(key) + jsch.addIdentity(securityString) session = jsch.getSession(user, host) } } + session.setConfig("StrictHostKeyChecking", "no") + session.connect() - channel = session.openChannel("shell") + channel = session.openChannel("exec") as ChannelExec + channel.setCommand(command) + channel.inputStream = null sshInputStream = channel.inputStream - sshOutputStream = channel.outputStream - channel.connect(5000) + channel.connect(CogboardConstants.Props.SSH_TIMEOUT) + } - if (channel.isConnected) { - executeCommandAndSendResult(config) + private fun getAuthenticationString( + authenticationType: AuthenticationType, + password: String, + token: String, + sshKey: String + ): String { + return when (authenticationType) { + AuthenticationType.BASIC -> password + AuthenticationType.TOKEN -> token + AuthenticationType.SSH_KEY -> sshKey } } private fun executeCommandAndSendResult(config: JsonObject) { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) - val logLines = config.getString(CogboardConstants.Props.LOG_LINES) ?: "0" - val logFilePath = config.getString(CogboardConstants.Props.LOG_FILE_PATH) ?: "" - val command = "cat $logFilePath | tail -$logLines" - sshOutputStream.write(command.toByteArray()) val responseBuffer = Buffer.buffer() - Delegates.observable(sshInputStream.available()) { _, _, newValue -> - if (newValue > 0) { - responseBuffer.appendBytes(sshInputStream.readAllBytes()) - } - vertx.eventBus().send(eventBusAddress, responseBuffer) - channel.disconnect() - session.disconnect() + while (sshInputStream.available() > 0) { + responseBuffer.appendBytes(sshInputStream.readAllBytes()) } + + vertx.eventBus().send(eventBusAddress, responseBuffer) + channel.disconnect() + session.disconnect() } private fun getAuthenticationType( - authenticationTypes: JsonArray, - user: String, - token: String, - pass: String, - key: String + authenticationTypes: JsonArray, + user: String, + token: String, + pass: String, + key: String ): AuthenticationType { return authenticationTypes.stream() @@ -119,11 +147,11 @@ class SSHClient : AbstractVerticle() { } private fun hasAuthTypeCorrectCredentials( - authType: AuthenticationType, - username: String, - token: String, - pass: String, - key: String + authType: AuthenticationType, + username: String, + token: String, + pass: String, + key: String ): Boolean { return when { authType == AuthenticationType.TOKEN && username.isNotBlank() && token.isNotBlank() -> true @@ -131,4 +159,4 @@ class SSHClient : AbstractVerticle() { else -> authType == AuthenticationType.BASIC && username.isNotBlank() && pass.isNotBlank() } } -} \ No newline at end of file +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt deleted file mode 100644 index 160211926..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHReceiver.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.cognifide.cogboard.ssh - -import com.cognifide.cogboard.CogboardConstants.* -import io.vertx.core.Handler -import io.vertx.core.Vertx -import io.vertx.core.eventbus.Message -import io.vertx.core.eventbus.MessageConsumer -import io.vertx.core.json.JsonObject -import java.nio.Buffer - -interface SSHReceiver { - val vertx: Vertx - val user: String - val password: String - val sshKey: String - val host: String - val logPath: String - val logLines: Int - val handler: Handler> - var consumer: MessageConsumer? - - fun registerForSSH(eventBusAddress: String) { - consumer = vertx.eventBus().consumer(eventBusAddress, handler) - } - - fun unregisterFromSSH() { - consumer?.let { - it.unregister() - } - } - - fun sendRequestForLogs(config: JsonObject) { - config.getString(Props.USER) ?: config.put(Props.USER, user) - config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, password) - config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, sshKey) - config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, host) - config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, logPath) - config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, logLines.toString()) - - vertx.eventBus().send(Event.SSH_COMMAND, config) - } -} \ No newline at end of file diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt index d8645437d..abca29a42 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt @@ -4,4 +4,4 @@ enum class AuthenticationType { BASIC, TOKEN, SSH_KEY -} \ No newline at end of file +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt index 80dd5cf97..d88fc7860 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt @@ -97,7 +97,7 @@ abstract class AsyncWidget( ) } - private fun basicProps(url: String): JsonObject { + protected fun basicProps(url: String): JsonObject { return JsonObject() .put(Props.URL, url) .put(Props.EVENT_ADDRESS, eventBusAddress) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt new file mode 100644 index 000000000..56c459c37 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt @@ -0,0 +1,49 @@ +package com.cognifide.cogboard.widget + +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.Event +import com.cognifide.cogboard.config.service.BoardsConfigService +import io.vertx.core.Vertx +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.JsonObject +import java.nio.Buffer + +abstract class SSHWidget( + vertx: Vertx, + config: JsonObject, + serv: BoardsConfigService +) : AsyncWidget(vertx, config, serv) { + val sshKey: String = config.endpointProp(Props.SSH_KEY) + val host: String = config.endpointProp(Props.SSH_HOST) + val logPath: String = config.endpointProp(Props.LOG_FILE_PATH) + val logLines: String = config.endpointProp(Props.LOG_LINES) + private lateinit var sshConsumer: MessageConsumer + + fun registerForSSH(eventBusAddress: String) { + sshConsumer = vertx.eventBus() + .consumer(eventBusAddress) + .handler { + handleSSHResponse(it.body()) + } + } + + abstract fun handleSSHResponse(body: Buffer?) + + fun unregisterFromSSH() { + if (::sshConsumer.isInitialized) { + sshConsumer.unregister() + } + } + + fun sendRequestForLogs(config: JsonObject) { + config.getString(Props.USER) ?: config.put(Props.USER, user) + config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, password) + config.getString(Props.TOKEN) ?: config.put(Props.TOKEN, token) + config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, sshKey) + config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, host) + config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, logPath) + config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, logLines) + + vertx.eventBus().send(Event.SSH_COMMAND, config) + } +} diff --git a/knotx/conf/cogboard.conf b/knotx/conf/cogboard.conf index a649461f1..0807a0c56 100644 --- a/knotx/conf/cogboard.conf +++ b/knotx/conf/cogboard.conf @@ -7,6 +7,7 @@ modules { userController = "com.cognifide.cogboard.config.controller.UserController" versionController = "com.cognifide.cogboard.config.controller.VersionController" httpClient = "com.cognifide.cogboard.http.HttpClient" + sshClient = "com.cognifide.cogboard.ssh.SSHClient" } ########### Modules configurations ########### From e281f80bca00b908e53d59b7cd9d2e8e12f06cc9 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Fri, 17 Sep 2021 23:58:22 +0200 Subject: [PATCH 007/226] Fixes for authorization via SSH key --- .../cognifide/cogboard/CogboardConstants.kt | 1 + .../com/cognifide/cogboard/ssh/SSHClient.kt | 81 ++++++++++++------- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index 5aa18c4ca..7ce481875 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -44,6 +44,7 @@ class CogboardConstants { const val LOG_LINES = "logLines" const val LOG_FILE_PATH = "logFilePath" const val SSH_KEY = "sshKey" + const val SSH_KEY_PASSPHRASE = "sshKeyPassphrase" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" const val USER = "user" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 94321d93b..668a2a686 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -4,6 +4,7 @@ import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.ssh.auth.AuthenticationType import com.jcraft.jsch.ChannelExec import com.jcraft.jsch.JSch +import com.jcraft.jsch.JSchException import com.jcraft.jsch.Session import io.vertx.core.AbstractVerticle import io.vertx.core.buffer.Buffer @@ -34,11 +35,20 @@ class SSHClient : AbstractVerticle() { .consumer(CogboardConstants.Event.SSH_COMMAND) .handler { message -> message.body()?.let { - connect(it) + tryToConnect(it) } } } + fun tryToConnect(config: JsonObject) { + val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) + try { + connect(config) + } catch (e: Exception) { + vertx.eventBus().send(eventBusAddress, e) + } + } + fun connect(config: JsonObject) { val user = config.getString(CogboardConstants.Props.USER) ?: "" val pass = config.getString(CogboardConstants.Props.PASSWORD) ?: "" @@ -53,13 +63,9 @@ class SSHClient : AbstractVerticle() { val authenticationType = getAuthenticationType(authTypes as JsonArray, user, token, pass, key) - val securityString: String = getAuthenticationString(authenticationType, pass, token, key) + createSSHChannel(authenticationType, host, user, config) - createSSHChannel(authenticationType, host, user, securityString, createCommand(config)) - - if (channel.isConnected) { - executeCommandAndSendResult(config) - } + executeCommandAndSendResult(config) } private fun createCommand(config: JsonObject): String { @@ -73,11 +79,33 @@ class SSHClient : AbstractVerticle() { authenticationType: AuthenticationType, host: String, user: String, - securityString: String, // password / token / sshKey - command: String + config: JsonObject ) { - jsch = JSch() + initJsch(authenticationType, host, user, config) + val command = createCommand(config) + if (session.isConnected) { + channel = session.openChannel("exec") as ChannelExec + + channel.setCommand(command) + channel.inputStream = null + sshInputStream = channel.inputStream + + channel.connect(CogboardConstants.Props.SSH_TIMEOUT) + } + } + + private fun initJsch( + authenticationType: AuthenticationType, + host: String, + user: String, + config: JsonObject + ) { + val securityString = getAuthenticationString(authenticationType, config) + val passphrase = config.getString(CogboardConstants.Props.SSH_KEY_PASSPHRASE) + + jsch = JSch() + jsch.setKnownHosts("~/.ssh/known_hosts") when (authenticationType) { AuthenticationType.BASIC -> { session = jsch.getSession(user, host) @@ -88,32 +116,26 @@ class SSHClient : AbstractVerticle() { session.setPassword(securityString) } AuthenticationType.SSH_KEY -> { - jsch.addIdentity(securityString) + if (passphrase == null) { + jsch.addIdentity(securityString) + } else { + jsch.addIdentity(securityString, passphrase) + } session = jsch.getSession(user, host) + session.setConfig("PreferredAuthentications", "publickey") } } - session.setConfig("StrictHostKeyChecking", "no") - session.connect() - - channel = session.openChannel("exec") as ChannelExec - - channel.setCommand(command) - channel.inputStream = null - sshInputStream = channel.inputStream - - channel.connect(CogboardConstants.Props.SSH_TIMEOUT) + session.connect(CogboardConstants.Props.SSH_TIMEOUT) } private fun getAuthenticationString( authenticationType: AuthenticationType, - password: String, - token: String, - sshKey: String + config: JsonObject ): String { return when (authenticationType) { - AuthenticationType.BASIC -> password - AuthenticationType.TOKEN -> token - AuthenticationType.SSH_KEY -> sshKey + AuthenticationType.BASIC -> config.getString(CogboardConstants.Props.PASSWORD) + AuthenticationType.TOKEN -> config.getString(CogboardConstants.Props.TOKEN) + AuthenticationType.SSH_KEY -> config.getString(CogboardConstants.Props.SSH_KEY) } } @@ -121,10 +143,7 @@ class SSHClient : AbstractVerticle() { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) val responseBuffer = Buffer.buffer() - - while (sshInputStream.available() > 0) { - responseBuffer.appendBytes(sshInputStream.readAllBytes()) - } + responseBuffer.appendBytes(sshInputStream.readAllBytes()) vertx.eventBus().send(eventBusAddress, responseBuffer) channel.disconnect() From a7b6225bde3bb503d0f15b0cb1df39b3063599a0 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sat, 18 Sep 2021 00:17:04 +0200 Subject: [PATCH 008/226] Minor SSH exception catching tweaks --- .../src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 668a2a686..e8c78f2dd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -44,7 +44,7 @@ class SSHClient : AbstractVerticle() { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) try { connect(config) - } catch (e: Exception) { + } catch (e: JSchException) { vertx.eventBus().send(eventBusAddress, e) } } From 3280f306d13622a871cc786efc4cc47bfe807be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 19 Sep 2021 20:01:12 +0200 Subject: [PATCH 009/226] Add log viewer toolbar UI Co-authored-by: clmrv --- .../Toolbar/DateRangePicker/index.js | 15 ++++ .../Toolbar/FilterPicker/index.js | 80 +++++++++++++++++++ .../Toolbar/FilterPicker/styled.js | 10 +++ .../Toolbar/SearchInput/index.js | 16 ++++ .../Toolbar/SearchInput/styled.js | 14 ++++ .../LogViewerWidget/Toolbar/ToolbarGroup.js | 30 +++++++ .../types/LogViewerWidget/Toolbar/index.js | 36 +++++++++ .../types/LogViewerWidget/Toolbar/styled.js | 18 +++++ .../widgets/types/LogViewerWidget/index.js | 4 +- 9 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js new file mode 100644 index 000000000..4f5985d0d --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import DateFnsUtils from '@date-io/date-fns'; +import ToolbarGroup from '../ToolbarGroup'; +import { MuiPickersUtilsProvider, DateTimePicker } from '@material-ui/pickers'; + +export default function DateRangePicker() { + return ( + + + + + + + ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js new file mode 100644 index 000000000..ad8671312 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { + Button, + Select, + Chip, + MenuItem, + FormControl, + InputLabel +} from '@material-ui/core'; +import { ScrollableBox } from './styled'; +import ToolbarGroup from '../ToolbarGroup'; +import { useState } from 'react'; + +export default function FilterPicker() { + const handleDelete = name => { + setFilters(filters.filter(item => item !== name)); + }; + + const [filters, setFilters] = useState([]); + const [logLevel, setLogLevel] = useState(''); + + return ( + + + Filters + + + + + Log level + + + + + ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js new file mode 100644 index 000000000..4a49caa64 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js @@ -0,0 +1,10 @@ +import styled from '@emotion/styled/macro'; +import { Box } from '@material-ui/core'; + +export const ScrollableBox = styled(Box)` + overflow-x: scroll; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js new file mode 100644 index 000000000..38f7f3ced --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -0,0 +1,16 @@ +import React from 'react'; + +import { TextField } from '@material-ui/core'; +import { Wrapper, CustomIconButton } from './styled'; +import SearchIcon from '@material-ui/icons/Search'; + +export default function SearchInput() { + return ( + + + + + + + ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js new file mode 100644 index 000000000..9f3e335d7 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js @@ -0,0 +1,14 @@ +import styled from '@emotion/styled/macro'; +import { IconButton } from '@material-ui/core'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: row; + position: relative; +`; + +export const CustomIconButton = styled(IconButton)` + position: absolute; + bottom: 0; + right: 0; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js new file mode 100644 index 000000000..d5db5249d --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js @@ -0,0 +1,30 @@ +import React from 'react'; +import styled from '@emotion/styled/macro'; +import { string } from 'prop-types'; +import { Typography } from '@material-ui/core'; + +const Wrapper = styled.div` + display: grid; + grid-template-rows: 24px auto; +`; + +const GroupContainer = styled.div` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: flex-end; + gap: 5px; +`; + +export default function ToolbarGroup({ title, children }) { + return ( + + {title} + {children} + + ); +} + +ToolbarGroup.propTypes = { + title: string +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js new file mode 100644 index 000000000..eeebf1285 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { Button, useTheme } from '@material-ui/core'; +import { RedButton, ScrollableDiv } from './styled'; +import SearchInput from './SearchInput'; +import ToolbarGroup from './ToolbarGroup'; +import DateRangePicker from './DateRangePicker'; +import GetAppIcon from '@material-ui/icons/GetApp'; +import DeleteIcon from '@material-ui/icons/Delete'; +import FilterPicker from './FilterPicker'; + +export default function Toolbar() { + const theme = useTheme(); + + return ( + + + + + + + + + + + + + + Clear logs + + + + ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js new file mode 100644 index 000000000..92e942a90 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -0,0 +1,18 @@ +import styled from '@emotion/styled/macro'; +import { Button } from '@material-ui/core'; + +export const RedButton = styled(Button)` + color: white; + background-color: ${({ theme }) => theme.palette.status.FAIL}; +`; + +export const ScrollableDiv = styled.div` + overflow-x: hidden; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: stretch; + justify-content: space-between; + gap: 16px; + overflow-x: scroll; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 382a16c1e..a17804a81 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,6 +1,8 @@ import React from 'react'; import { number, string } from 'prop-types'; +import Toolbar from './Toolbar'; + const LogViewerWidget = ({ id, endpoint, @@ -10,7 +12,7 @@ const LogViewerWidget = ({ logFileSizeField, logRecordExpirationField }) => { - return <>; + return ; }; LogViewerWidget.propTypes = { From 291be473ce28648c1dab4914e4a31e2b45042cda Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sat, 25 Sep 2021 18:18:57 +0200 Subject: [PATCH 010/226] Created LogViewerWidget --- .../cognifide/cogboard/CogboardConstants.kt | 2 ++ .../cogboard/widget/type/LogViewerWidget.kt | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index b781386a9..1f9433f23 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -39,6 +39,8 @@ class CogboardConstants { const val SCHEDULE_PERIOD_DEFAULT = 120L // 120 seconds const val SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds const val URL = "url" + + const val LOG_LINES = "logLines" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" const val USER = "user" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt new file mode 100644 index 000000000..bc8e2bb47 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -0,0 +1,31 @@ +package com.cognifide.cogboard.widget.type + +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.config.service.BoardsConfigService +import com.cognifide.cogboard.widget.AsyncWidget +import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject + +class LogViewerWidget( + vertx: Vertx, + config: JsonObject, + serv: BoardsConfigService +): AsyncWidget(vertx, config, serv) { + private val lines = config.getInteger(Props.LOG_LINES) + + override fun updateState() { + if (url.isNotBlank()) { + httpGet("$url?lines=$lines") + } else { + sendConfigurationError("Endpoint URL is blank") + } + } + + override fun handleResponse(responseBody: JsonObject) { + if (checkAuthorized(responseBody)) { + val logs = responseBody.getString(Props.LOG_LINES) + + send(logs) + } + } +} \ No newline at end of file From af06dd9bba205af7b8d340fcc53221b77a931b65 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sat, 25 Sep 2021 18:57:32 +0200 Subject: [PATCH 011/226] Added LogViewer to WidgetIndex --- .../cognifide/cogboard/widget/WidgetIndex.kt | 18 +++--------------- .../cogboard/widget/type/LogViewerWidget.kt | 10 +++++----- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt index 70e8e7992..519e9d534 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt @@ -3,20 +3,7 @@ package com.cognifide.cogboard.widget import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.storage.VolumeStorageFactory.appConfig -import com.cognifide.cogboard.widget.type.AemBundleInfoWidget -import com.cognifide.cogboard.widget.type.AemHealthcheckWidget -import com.cognifide.cogboard.widget.type.BambooDeploymentWidget -import com.cognifide.cogboard.widget.type.BambooPlanWidget -import com.cognifide.cogboard.widget.type.CheckboxWidget -import com.cognifide.cogboard.widget.type.IframeEmbedWidget -import com.cognifide.cogboard.widget.type.JenkinsJobWidget -import com.cognifide.cogboard.widget.type.JiraBucketsWidget -import com.cognifide.cogboard.widget.type.LinkListWidget -import com.cognifide.cogboard.widget.type.WhiteSpaceWidget -import com.cognifide.cogboard.widget.type.ServiceCheckWidget -import com.cognifide.cogboard.widget.type.TextWidget -import com.cognifide.cogboard.widget.type.ToDoListWidget -import com.cognifide.cogboard.widget.type.WorldClockWidget +import com.cognifide.cogboard.widget.type.* import com.cognifide.cogboard.widget.type.randompicker.RandomPickerWidget import com.cognifide.cogboard.widget.type.sonarqube.SonarQubeWidget import com.cognifide.cogboard.widget.type.zabbix.ZabbixWidget @@ -48,7 +35,8 @@ class WidgetIndex { "Jira Buckets" to JiraBucketsWidget::class.java, "Service Check" to ServiceCheckWidget::class.java, "SonarQube" to SonarQubeWidget::class.java, - "White Space" to WhiteSpaceWidget::class.java + "White Space" to WhiteSpaceWidget::class.java, + "Log Viewer" to LogViewerWidget::class.java ) fun availableWidgets(): JsonArray { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index bc8e2bb47..20745f323 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -7,10 +7,10 @@ import io.vertx.core.Vertx import io.vertx.core.json.JsonObject class LogViewerWidget( - vertx: Vertx, - config: JsonObject, - serv: BoardsConfigService -): AsyncWidget(vertx, config, serv) { + vertx: Vertx, + config: JsonObject, + serv: BoardsConfigService +) : AsyncWidget(vertx, config, serv) { private val lines = config.getInteger(Props.LOG_LINES) override fun updateState() { @@ -28,4 +28,4 @@ class LogViewerWidget( send(logs) } } -} \ No newline at end of file +} From cd3a269f689d22350682488577c5429870604dc0 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sat, 25 Sep 2021 18:58:03 +0200 Subject: [PATCH 012/226] Added WireMock for LogViewer --- api-mocks/__files/logViewer/logs.json | 3 +++ api-mocks/mappings/endpoints-mapping.json | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 api-mocks/__files/logViewer/logs.json diff --git a/api-mocks/__files/logViewer/logs.json b/api-mocks/__files/logViewer/logs.json new file mode 100644 index 000000000..b4211a0e3 --- /dev/null +++ b/api-mocks/__files/logViewer/logs.json @@ -0,0 +1,3 @@ +{ + "logLines": "02:49:12 127.0.0.1 GET / 200\n02:49:35 127.0.0.1 GET /index.html 200\n03:01:06 127.0.0.1 GET /images/sponsered.gif 304\n03:52:36 127.0.0.1 GET /search.php 200\n04:17:03 127.0.0.1 GET /admin/style.css 200\n05:04:54 127.0.0.1 GET /favicon.ico 404\n05:38:07 127.0.0.1 GET /js/ads.js 200\n" +} \ No newline at end of file diff --git a/api-mocks/mappings/endpoints-mapping.json b/api-mocks/mappings/endpoints-mapping.json index 5a3ade401..8dc85fa63 100644 --- a/api-mocks/mappings/endpoints-mapping.json +++ b/api-mocks/mappings/endpoints-mapping.json @@ -1,5 +1,23 @@ { "mappings": [ + { + "request": { + "method": "GET", + "url": "/logviewer", + "queryParameters": { + "lines": { + "matches": "^[0-9]+$" + } + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFileName": "logViewer/logs.json" + } + }, { "request": { "method": "GET", From 9c41deff50d97a6f9a7fd82d5a7a3faff9249f33 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 25 Sep 2021 23:54:10 +0200 Subject: [PATCH 013/226] Setup datepicker, toolbar layout tweaks #377 --- cogboard-webapp/package-lock.json | 53 ++++++++++++++++++- cogboard-webapp/package.json | 2 + .../Toolbar/DateRangePicker/index.js | 24 ++++++--- .../Toolbar/SearchInput/index.js | 2 +- .../types/LogViewerWidget/Toolbar/index.js | 6 +-- .../types/LogViewerWidget/Toolbar/styled.js | 4 +- cogboard-webapp/src/theme.js | 6 +++ 7 files changed, 81 insertions(+), 16 deletions(-) diff --git a/cogboard-webapp/package-lock.json b/cogboard-webapp/package-lock.json index adebfe522..1ec0e5862 100644 --- a/cogboard-webapp/package-lock.json +++ b/cogboard-webapp/package-lock.json @@ -1194,6 +1194,19 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, + "@date-io/core": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz", + "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" + }, + "@date-io/moment": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-1.3.13.tgz", + "integrity": "sha512-3kJYusJtQuOIxq6byZlzAHoW/18iExJer9qfRF5DyyzdAk074seTuJfdofjz4RFfTd/Idk8WylOQpWtERqvFuQ==", + "requires": { + "@date-io/core": "^1.3.13" + } + }, "@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -1935,6 +1948,19 @@ "@babel/runtime": "^7.4.4" } }, + "@material-ui/pickers": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.3.10.tgz", + "integrity": "sha512-hS4pxwn1ZGXVkmgD4tpFpaumUaAg2ZzbTrxltfC5yPw4BJV+mGkfnQOB4VpWEYZw2jv65Z0wLwDE/piQiPPZ3w==", + "requires": { + "@babel/runtime": "^7.6.0", + "@date-io/core": "1.x", + "@types/styled-jsx": "^2.2.8", + "clsx": "^1.0.2", + "react-transition-group": "^4.0.0", + "rifm": "^0.7.0" + } + }, "@material-ui/styles": { "version": "4.11.4", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", @@ -2487,6 +2513,14 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==" }, + "@types/styled-jsx": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.9.tgz", + "integrity": "sha512-W/iTlIkGEyTBGTEvZCey8EgQlQ5l0DwMqi3iOXlLs2kyBwYTXHKEiU6IZ5EwoRwngL8/dGYuzezSup89ttVHLw==", + "requires": { + "@types/react": "*" + } + }, "@types/tapable": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz", @@ -14218,6 +14252,14 @@ "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" }, + "rifm": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz", + "integrity": "sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==", + "requires": { + "@babel/runtime": "^7.3.1" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -16575,6 +16617,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -16592,6 +16635,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -16622,6 +16666,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -16633,6 +16678,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -16682,12 +16728,14 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "optional": true }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -16696,6 +16744,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -16706,6 +16755,7 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -16761,6 +16811,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "optional": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" diff --git a/cogboard-webapp/package.json b/cogboard-webapp/package.json index a470304ac..8e5007ffd 100644 --- a/cogboard-webapp/package.json +++ b/cogboard-webapp/package.json @@ -3,10 +3,12 @@ "version": "0.1.0", "private": true, "dependencies": { + "@date-io/moment": "^1.3.13", "@emotion/core": "^10.0.28", "@emotion/styled": "^10.0.27", "@material-ui/core": "^4.9.11", "@material-ui/icons": "^4.9.1", + "@material-ui/pickers": "^3.3.10", "@reach/router": "^1.3.3", "chartist": "^0.11.4", "copy-to-clipboard": "^3.3.1", diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js index 4f5985d0d..1363a61c1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js @@ -1,15 +1,23 @@ import React from 'react'; -import DateFnsUtils from '@date-io/date-fns'; -import ToolbarGroup from '../ToolbarGroup'; +import MomentUtils from '@date-io/moment'; import { MuiPickersUtilsProvider, DateTimePicker } from '@material-ui/pickers'; +import ToolbarGroup from '../ToolbarGroup'; export default function DateRangePicker() { return ( - - - - - - + + + console.log('TODO')} + /> + console.log('TODO')} + /> + + ); } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index 38f7f3ced..4ca23327f 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -7,7 +7,7 @@ import SearchIcon from '@material-ui/icons/Search'; export default function SearchInput() { return ( - + diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index eeebf1285..7f1705bd1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { Button, useTheme } from '@material-ui/core'; -import { RedButton, ScrollableDiv } from './styled'; +import { RedButton, Wrapper } from './styled'; import SearchInput from './SearchInput'; import ToolbarGroup from './ToolbarGroup'; import DateRangePicker from './DateRangePicker'; @@ -12,7 +12,7 @@ export default function Toolbar() { const theme = useTheme(); return ( - + @@ -31,6 +31,6 @@ export default function Toolbar() { Clear logs - + ); } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index 92e942a90..44500dc62 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -6,13 +6,11 @@ export const RedButton = styled(Button)` background-color: ${({ theme }) => theme.palette.status.FAIL}; `; -export const ScrollableDiv = styled.div` +export const Wrapper = styled.div` overflow-x: hidden; display: flex; flex-direction: row; - flex-wrap: wrap; align-items: stretch; justify-content: space-between; gap: 16px; - overflow-x: scroll; `; diff --git a/cogboard-webapp/src/theme.js b/cogboard-webapp/src/theme.js index de1b6498d..acd01ff55 100644 --- a/cogboard-webapp/src/theme.js +++ b/cogboard-webapp/src/theme.js @@ -25,6 +25,12 @@ export const theme = createMuiTheme({ marginTop: 0, marginRight: 0 } + }, + // Datepicker dialog + MuiDialogActions: { + root: { + backgroundColor: COLORS.WHITE + } } }, palette: { From 7f9eb93301c9179174e94d36e400289c3b8a6226 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 26 Sep 2021 02:51:22 +0200 Subject: [PATCH 014/226] Log table layout setup #378 --- .../types/LogViewerWidget/LogList/LogEntry.js | 42 +++++++++++ .../types/LogViewerWidget/LogList/index.js | 37 ++++++++++ .../types/LogViewerWidget/LogList/styled.js | 69 +++++++++++++++++++ .../widgets/types/LogViewerWidget/index.js | 10 ++- .../widgets/types/LogViewerWidget/styled.js | 9 +++ 5 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js new file mode 100644 index 000000000..df16dd81a --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { AccordionSummary, AccordionDetails } from '@material-ui/core'; +import { GridSchema, Text, CustomAccordion } from './styled'; + +export default function LogEntry() { + return ( + + + + INFO + 2021-04-22 14:08:37 + mongodb.log + + {'Expected corresponding JSX closing tag for .'} + + + + + +
+
+ ID + Type + IP address + Port +
+
+ 123456 + sys + 127.0.0.1 + 27017 +
+ + { + 'SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)' + } + +
+
+
+ ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js new file mode 100644 index 000000000..af4f4ecc8 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import LogEntry from './LogEntry'; +import { + Container, + Header, + GridSchema, + ColumnTitle, + LogsWrapper +} from './styled'; + +export default function LogList() { + return ( + +
+ + Level + Date + Provider + Message + +
+ + + + + + + + + + + + + +
+ ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js new file mode 100644 index 000000000..3ce802cb4 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -0,0 +1,69 @@ +import styled from '@emotion/styled/macro'; +import { Typography, Accordion } from '@material-ui/core'; + +export const Container = styled.div` + max-height: 100%; + display: grid; + padding-top: 20px; + grid-template-rows: 34px 1fr; +`; + +export const Header = styled.div` + border-bottom: 1px solid rgba(255, 255, 255, 0.5); + border-left: none; + border-right: none; + + padding: 6px 0; +`; + +export const GridSchema = styled.div` + display: grid; + grid-template-columns: 70px 150px 190px 1fr; + padding: 0 10px; +`; + +export const ColumnTitle = styled(Typography)` + font-weight: 600; + font-size: 0.85rem; +`; + +export const Text = styled(Typography)` + font-size: 0.8rem; +`; + +export const LogsWrapper = styled.div` + padding: 6px 0; + overflow-y: auto; + height: 100%; +`; + +export const CustomAccordion = styled(Accordion)` + margin: 2px 0; + + &.MuiPaper-root { + background-color: rgba(255, 255, 255, 0.1); + } + &.Mui-expanded { + margin: 8px 0; + } + + .MuiAccordionSummary-root { + padding: 4px; + min-height: unset; + } + + .MuiButtonBase-root { + padding-left: unset; + padding-right: unset; + } + .MuiAccordionSummary-content { + margin: 0; + } + .MuiAccordionSummary-content.Mui-expanded { + min-height: unset; + } + + .MuiAccordionDetails-root { + padding: 0 0 4px 0; + } +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index a17804a81..6dbd58af1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,7 +1,8 @@ import React from 'react'; import { number, string } from 'prop-types'; - import Toolbar from './Toolbar'; +import LogList from './LogList'; +import { Container } from './styled'; const LogViewerWidget = ({ id, @@ -12,7 +13,12 @@ const LogViewerWidget = ({ logFileSizeField, logRecordExpirationField }) => { - return ; + return ( + + + + + ); }; LogViewerWidget.propTypes = { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js new file mode 100644 index 000000000..593eb7ff6 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js @@ -0,0 +1,9 @@ +import styled from '@emotion/styled/macro'; + +export const Container = styled.div` + height: 100%; + width: 100%; + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 76px 1fr; +`; From 31745895ee08321ff3f70cbc3fc3e8f95d421747 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 27 Sep 2021 01:31:55 +0200 Subject: [PATCH 015/226] Add toolbar float, enchance LogEntry #378 --- .../types/LogViewerWidget/LogList/LogEntry.js | 73 ++++++++---- .../types/LogViewerWidget/LogList/index.js | 106 ++++++++++++++++-- .../types/LogViewerWidget/LogList/styled.js | 35 +++++- .../types/LogViewerWidget/Toolbar/styled.js | 2 + .../widgets/types/LogViewerWidget/styled.js | 4 +- 5 files changed, 177 insertions(+), 43 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index df16dd81a..fcb004f76 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,42 +1,67 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { string, number, bool, objectOf, oneOfType } from 'prop-types'; import { AccordionSummary, AccordionDetails } from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { GridSchema, Text, CustomAccordion } from './styled'; -export default function LogEntry() { +export default function LogEntry({ + type, + date, + provider, + shortMsg, + fullMsg, + additionalData +}) { + const [expanded, setExpanded] = useState(false); + + const additionalDataNames = additionalData && Object.keys(additionalData); + return ( - - + + setExpanded(!expanded)} + expandIcon={expanded && } + > - INFO - 2021-04-22 14:08:37 - mongodb.log - - {'Expected corresponding JSX closing tag for .'} - + {type?.toUpperCase()} + {date} + {provider} + {shortMsg} -
+
{/* first column empty */}
- ID - Type - IP address - Port + {additionalDataNames.map((name, index) => ( + {name} + ))}
- 123456 - sys - 127.0.0.1 - 27017 + {additionalDataNames.map((name, index) => ( + {`${additionalData[name]}`} + ))}
- - { - 'SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)' - } - + {fullMsg}
); } + +LogEntry.propTypes = { + type: string, + date: string.isRequired, + provider: string, + shortMsg: string, + fullMsg: string, + additionalData: objectOf(oneOfType([string, number, bool])) +}; + +LogEntry.defaultProps = { + type: 'info', + provider: '', + shortMsg: '', + fullMsg: '', + additionalData: {} +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index af4f4ecc8..aadda6f12 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -8,6 +8,20 @@ import { LogsWrapper } from './styled'; +const testData = { + date: '2021-04-22 14:08:37', + provider: 'mongodb.log', + fullMsg: + 'SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)', + shortMsg: 'Expected corresponding JSX closing tag for .', + additionalData: { + ID: '123456', + Type: 'sys', + 'IP address': '127.0.0.1', + Port: '27017' + } +}; + export default function LogList() { return ( @@ -21,16 +35,88 @@ export default function LogList() { - - - - - - - - - - + {/* static presentation */} + + + + + + + + + + ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 3ce802cb4..ab7df4bea 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -1,10 +1,11 @@ import styled from '@emotion/styled/macro'; +import { COLORS } from '../../../../../constants'; import { Typography, Accordion } from '@material-ui/core'; export const Container = styled.div` max-height: 100%; display: grid; - padding-top: 20px; + padding-top: 76px; grid-template-rows: 34px 1fr; `; @@ -27,9 +28,24 @@ export const ColumnTitle = styled(Typography)` font-size: 0.85rem; `; -export const Text = styled(Typography)` - font-size: 0.8rem; -`; +export const Text = styled(Typography)(props => { + const getColor = type => + ({ + info: COLORS.WHITE, + success: COLORS.GREEN, + warn: COLORS.YELLOW, + error: COLORS.RED + }[type.toLowerCase()]); + + return ` + font-size: 0.8rem; + ${props.type && + ` + font-weight: 500; + color: ${getColor(props.type)}; + `} + `; +}); export const LogsWrapper = styled.div` padding: 6px 0; @@ -52,12 +68,19 @@ export const CustomAccordion = styled(Accordion)` min-height: unset; } + .MuiButtonBase-root .MuiIconButton-root { + position: absolute; + top: 0; + right: 16px; + } + .MuiButtonBase-root { - padding-left: unset; - padding-right: unset; + padding: unset; } + .MuiAccordionSummary-content { margin: 0; + padding: 4px 0; } .MuiAccordionSummary-content.Mui-expanded { min-height: unset; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index 44500dc62..7193f8cc7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -7,6 +7,8 @@ export const RedButton = styled(Button)` `; export const Wrapper = styled.div` + top: 0; + position: absolute; overflow-x: hidden; display: flex; flex-direction: row; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js index 593eb7ff6..c5244f6ac 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js @@ -1,9 +1,7 @@ import styled from '@emotion/styled/macro'; export const Container = styled.div` + position: relative; height: 100%; width: 100%; - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 76px 1fr; `; From 0a168b9312ee2bea5f9448facfaef3d6e9f4d428 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 30 Sep 2021 18:07:25 +0200 Subject: [PATCH 016/226] Enchance log entry collapse style #378 --- .../components/widgets/types/LogViewerWidget/LogList/styled.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index ab7df4bea..c61c33f71 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -87,6 +87,7 @@ export const CustomAccordion = styled(Accordion)` } .MuiAccordionDetails-root { - padding: 0 0 4px 0; + padding: 4px 0; + background-color: rgba(0, 0, 0, 0.15); } `; From 38d9e52ac263424283af39aff93633ffa948b44b Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 30 Sep 2021 20:19:31 +0200 Subject: [PATCH 017/226] Add variable section to LogList #378 --- .../types/LogViewerWidget/LogList/LogEntry.js | 86 ++++++++++------ .../types/LogViewerWidget/LogList/helpers.js | 6 ++ .../types/LogViewerWidget/LogList/index.js | 98 +++++++++---------- .../types/LogViewerWidget/LogList/styled.js | 12 ++- 4 files changed, 115 insertions(+), 87 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index fcb004f76..a0184d625 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,20 +1,48 @@ import React, { useState } from 'react'; -import { string, number, bool, objectOf, oneOfType } from 'prop-types'; +import { + string, + number, + bool, + shape, + oneOfType, + arrayOf, + objectOf +} from 'prop-types'; import { AccordionSummary, AccordionDetails } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { GridSchema, Text, CustomAccordion } from './styled'; +import { + GridSchema, + Text, + CustomAccordion, + VariableGridSchema +} from './styled'; +import { getGridTemplate } from './helpers'; -export default function LogEntry({ - type, - date, - provider, - shortMsg, - fullMsg, - additionalData -}) { +export default function LogEntry({ type, date, additionalData, variableData }) { const [expanded, setExpanded] = useState(false); - const additionalDataNames = additionalData && Object.keys(additionalData); + const AdditionalData = ({ names }) => { + const additionalDataNames = additionalData && Object.keys(additionalData); + return ( +
+ {additionalDataNames.map((name, index) => ( + {names ? name : `${additionalData[name]}`} + ))} +
+ ); + }; + + const VariablePart = ({ description }) => { + const variableFieldsTemplate = getGridTemplate(variableData.template); + const data = description ? variableData.description : variableData.header; + return ( + + {data.map((text, index) => ( + {text} + ))} + + ); + }; return ( @@ -25,24 +53,14 @@ export default function LogEntry({ {type?.toUpperCase()} {date} - {provider} - {shortMsg} +
-
{/* first column empty */} -
- {additionalDataNames.map((name, index) => ( - {name} - ))} -
-
- {additionalDataNames.map((name, index) => ( - {`${additionalData[name]}`} - ))} -
- {fullMsg} + + +
@@ -52,16 +70,20 @@ export default function LogEntry({ LogEntry.propTypes = { type: string, date: string.isRequired, - provider: string, - shortMsg: string, - fullMsg: string, - additionalData: objectOf(oneOfType([string, number, bool])) + additionalData: objectOf(oneOfType([string, number, bool])), + variableData: shape({ + template: arrayOf(string).isRequired, + header: arrayOf(oneOfType([string, number, bool])).isRequired, + description: arrayOf(oneOfType([string, number, bool])).isRequired + }) }; LogEntry.defaultProps = { type: 'info', - provider: '', - shortMsg: '', - fullMsg: '', + variableData: { + template: [], + header: [], + description: [] + }, additionalData: {} }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js new file mode 100644 index 000000000..69245e4dd --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -0,0 +1,6 @@ +export const getGridTemplate = columnNames => { + const widths = columnNames.map(name => + name.toLowerCase() === 'message' ? '3fr ' : '1fr ' + ); + return widths.reduce((acc, current) => acc + current, ''); +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index aadda6f12..aab936ff9 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -5,117 +5,107 @@ import { Header, GridSchema, ColumnTitle, - LogsWrapper + LogsWrapper, + VariableGridSchema } from './styled'; +import { getGridTemplate } from './helpers'; +const testLogTemplate = ['Provider', 'Message']; const testData = { date: '2021-04-22 14:08:37', - provider: 'mongodb.log', - fullMsg: - 'SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)', - shortMsg: 'Expected corresponding JSX closing tag for .', additionalData: { ID: '123456', Type: 'sys', 'IP address': '127.0.0.1', Port: '27017' + }, + variableData: { + template: testLogTemplate, + header: [ + 'mongodb.log', + 'Expected corresponding JSX closing tag for .' + ], + description: [ + 'provider desc', + 'SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)' + ] } }; export default function LogList() { + const VariableLogListHeader = () => ( + + {testLogTemplate.map((name, index) => ( + {name} + ))} + + ); + return (
Level Date - Provider - Message +
{/* static presentation */} - +
diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index c61c33f71..23834b8ad 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -18,10 +18,18 @@ export const Header = styled.div` `; export const GridSchema = styled.div` + width: 100%; display: grid; - grid-template-columns: 70px 150px 190px 1fr; + grid-template-columns: 70px 150px 1fr; padding: 0 10px; `; +export const VariableGridSchema = styled.div( + props => ` + width: 100%; + display: grid; + grid-template-columns: ${props.template}; + ` +); export const ColumnTitle = styled(Typography)` font-weight: 600; @@ -38,7 +46,9 @@ export const Text = styled(Typography)(props => { }[type.toLowerCase()]); return ` + line-height: 19px; font-size: 0.8rem; + font-weight: 400; ${props.type && ` font-weight: 500; From 7604bbde793a5e69c7c177026406929e8c263b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 6 Oct 2021 19:40:21 +0200 Subject: [PATCH 018/226] Clean up the code --- .../Toolbar/DateRangePicker/index.js | 36 +++++++++---------- .../Toolbar/FilterPicker/index.js | 6 ++-- .../Toolbar/FilterPicker/styled.js | 1 + .../Toolbar/SearchInput/index.js | 20 +++++------ .../LogViewerWidget/Toolbar/ToolbarGroup.js | 30 ---------------- .../Toolbar/ToolbarGroup/index.js | 17 +++++++++ .../Toolbar/ToolbarGroup/styled.js | 14 ++++++++ .../types/LogViewerWidget/Toolbar/index.js | 6 ++-- .../widgets/types/LogViewerWidget/index.js | 12 +------ 9 files changed, 69 insertions(+), 73 deletions(-) delete mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js index 1363a61c1..2b73ad2a0 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js @@ -3,21 +3,21 @@ import MomentUtils from '@date-io/moment'; import { MuiPickersUtilsProvider, DateTimePicker } from '@material-ui/pickers'; import ToolbarGroup from '../ToolbarGroup'; -export default function DateRangePicker() { - return ( - - - console.log('TODO')} - /> - console.log('TODO')} - /> - - - ); -} +const DateRangePicker = () => ( + + + console.log('TODO')} + /> + console.log('TODO')} + /> + + +); + +export default DateRangePicker; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index ad8671312..1e6551d1b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -11,7 +11,7 @@ import { ScrollableBox } from './styled'; import ToolbarGroup from '../ToolbarGroup'; import { useState } from 'react'; -export default function FilterPicker() { +const FilterPicker = () => { const handleDelete = name => { setFilters(filters.filter(item => item !== name)); }; @@ -77,4 +77,6 @@ export default function FilterPicker() { ); -} +}; + +export default FilterPicker; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js index 4a49caa64..312484d59 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js @@ -4,6 +4,7 @@ import { Box } from '@material-ui/core'; export const ScrollableBox = styled(Box)` overflow-x: scroll; scrollbar-width: none; + &::-webkit-scrollbar { display: none; } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index 4ca23327f..d420317a2 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -4,13 +4,13 @@ import { TextField } from '@material-ui/core'; import { Wrapper, CustomIconButton } from './styled'; import SearchIcon from '@material-ui/icons/Search'; -export default function SearchInput() { - return ( - - - - - - - ); -} +const SearchInput = () => ( + + + + + + +); + +export default SearchInput; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js deleted file mode 100644 index d5db5249d..000000000 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled/macro'; -import { string } from 'prop-types'; -import { Typography } from '@material-ui/core'; - -const Wrapper = styled.div` - display: grid; - grid-template-rows: 24px auto; -`; - -const GroupContainer = styled.div` - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: flex-end; - gap: 5px; -`; - -export default function ToolbarGroup({ title, children }) { - return ( - - {title} - {children} - - ); -} - -ToolbarGroup.propTypes = { - title: string -}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js new file mode 100644 index 000000000..554b0c029 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { string } from 'prop-types'; +import { Typography } from '@material-ui/core'; +import { Wrapper, GroupContainer } from './styled'; + +const ToolbarGroup = ({ title, children }) => ( + + {title} + {children} + +); + +ToolbarGroup.propTypes = { + title: string +}; + +export default ToolbarGroup; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js new file mode 100644 index 000000000..9eb826bd4 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js @@ -0,0 +1,14 @@ +import styled from '@emotion/styled/macro'; + +export const Wrapper = styled.div` + display: grid; + grid-template-rows: 24px auto; +`; + +export const GroupContainer = styled.div` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: flex-end; + gap: 5px; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 7f1705bd1..fcd445b61 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -8,7 +8,7 @@ import GetAppIcon from '@material-ui/icons/GetApp'; import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; -export default function Toolbar() { +const Toolbar = () => { const theme = useTheme(); return ( @@ -33,4 +33,6 @@ export default function Toolbar() { ); -} +}; + +export default Toolbar; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index a17804a81..fd28f5630 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -3,17 +3,7 @@ import { number, string } from 'prop-types'; import Toolbar from './Toolbar'; -const LogViewerWidget = ({ - id, - endpoint, - schedulePeriod, - path, - logLinesField, - logFileSizeField, - logRecordExpirationField -}) => { - return ; -}; +const LogViewerWidget = () => ; LogViewerWidget.propTypes = { endpoint: string, From 8d81c1aa0a8f79a9ec51283d766c8f9edd848f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 6 Oct 2021 20:47:25 +0200 Subject: [PATCH 019/226] Change styles --- .../widgets/types/LogViewerWidget/LogList/LogEntry.js | 2 +- .../widgets/types/LogViewerWidget/LogList/helpers.js | 4 +++- .../widgets/types/LogViewerWidget/LogList/index.js | 6 ++++-- .../widgets/types/LogViewerWidget/LogList/styled.js | 2 +- .../types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js | 4 ++-- .../widgets/types/LogViewerWidget/Toolbar/styled.js | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index a0184d625..3999565ea 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -16,7 +16,7 @@ import { CustomAccordion, VariableGridSchema } from './styled'; -import { getGridTemplate } from './helpers'; +import getGridTemplate from './helpers'; export default function LogEntry({ type, date, additionalData, variableData }) { const [expanded, setExpanded] = useState(false); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 69245e4dd..25f6c24df 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -1,6 +1,8 @@ -export const getGridTemplate = columnNames => { +const getGridTemplate = columnNames => { const widths = columnNames.map(name => name.toLowerCase() === 'message' ? '3fr ' : '1fr ' ); return widths.reduce((acc, current) => acc + current, ''); }; + +export default getGridTemplate; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index aab936ff9..5ff41576a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import { useTheme } from '@material-ui/core'; import LogEntry from './LogEntry'; import { Container, @@ -8,7 +9,7 @@ import { LogsWrapper, VariableGridSchema } from './styled'; -import { getGridTemplate } from './helpers'; +import getGridTemplate from './helpers'; const testLogTemplate = ['Provider', 'Message']; const testData = { @@ -33,6 +34,7 @@ const testData = { }; export default function LogList() { + const theme = useTheme(); const VariableLogListHeader = () => ( {testLogTemplate.map((name, index) => ( @@ -43,7 +45,7 @@ export default function LogList() { return ( -
+
Level Date diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 23834b8ad..1db6f4f19 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -10,7 +10,7 @@ export const Container = styled.div` `; export const Header = styled.div` - border-bottom: 1px solid rgba(255, 255, 255, 0.5); + border-bottom: 1px solid ${({ theme }) => theme.palette.divider}; border-left: none; border-right: none; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js index 9eb826bd4..bcb20e07a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js @@ -2,7 +2,7 @@ import styled from '@emotion/styled/macro'; export const Wrapper = styled.div` display: grid; - grid-template-rows: 24px auto; + grid-template-rows: 1.7em auto; `; export const GroupContainer = styled.div` @@ -10,5 +10,5 @@ export const GroupContainer = styled.div` flex-direction: row; flex-wrap: nowrap; align-items: flex-end; - gap: 5px; + gap: 0.5em; `; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index 7193f8cc7..d3ec3eedb 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -14,5 +14,5 @@ export const Wrapper = styled.div` flex-direction: row; align-items: stretch; justify-content: space-between; - gap: 16px; + gap: 1em; `; From c6754cdbb9023512e8abaccba08d5b6aeaee2f27 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 7 Oct 2021 21:09:25 +0200 Subject: [PATCH 020/226] Change pixels to relative units, add two shades #378 --- .../types/LogViewerWidget/LogList/styled.js | 21 +++++++++---------- cogboard-webapp/src/constants/index.js | 4 +++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 1db6f4f19..2ff61248c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -5,16 +5,15 @@ import { Typography, Accordion } from '@material-ui/core'; export const Container = styled.div` max-height: 100%; display: grid; - padding-top: 76px; - grid-template-rows: 34px 1fr; + padding-top: 6em; + grid-template-rows: auto 1fr; `; export const Header = styled.div` border-bottom: 1px solid ${({ theme }) => theme.palette.divider}; border-left: none; border-right: none; - - padding: 6px 0; + padding: 0.25em 0; `; export const GridSchema = styled.div` @@ -67,21 +66,21 @@ export const CustomAccordion = styled(Accordion)` margin: 2px 0; &.MuiPaper-root { - background-color: rgba(255, 255, 255, 0.1); + background-color: ${COLORS.LIGHT_SHADE}; } &.Mui-expanded { - margin: 8px 0; + margin: 0.5em 0; } .MuiAccordionSummary-root { - padding: 4px; + padding: 0.25em; min-height: unset; } .MuiButtonBase-root .MuiIconButton-root { position: absolute; top: 0; - right: 16px; + right: 1em; } .MuiButtonBase-root { @@ -90,14 +89,14 @@ export const CustomAccordion = styled(Accordion)` .MuiAccordionSummary-content { margin: 0; - padding: 4px 0; + padding: 0.25em 0; } .MuiAccordionSummary-content.Mui-expanded { min-height: unset; } .MuiAccordionDetails-root { - padding: 4px 0; - background-color: rgba(0, 0, 0, 0.15); + padding: 0.25em 0; + background-color: ${COLORS.DARK_SHADE}; } `; diff --git a/cogboard-webapp/src/constants/index.js b/cogboard-webapp/src/constants/index.js index 71da9aa5f..3b83785d9 100644 --- a/cogboard-webapp/src/constants/index.js +++ b/cogboard-webapp/src/constants/index.js @@ -15,7 +15,9 @@ export const COLORS = { LIGHT_BLUE: '#bbdefb', BLUE: '#198cbd', PURPLE: '#26243e', - YELLOW: '#FECD00' + YELLOW: '#FECD00', + LIGHT_SHADE: 'rgba(255,255,255, 0.1)', + DARK_SHADE: 'rgba(0,0,0, 0.15)' }; export const URL = { From b7d21189b02dc180464d6a251f4f12ead7012f29 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Thu, 7 Oct 2021 23:40:14 +0200 Subject: [PATCH 021/226] Code fixes after review --- .../cognifide/cogboard/CogboardConstants.kt | 6 +- .../com/cognifide/cogboard/ssh/SSHClient.kt | 134 ++++-------------- .../cogboard/ssh/auth/SSHAuthData.kt | 49 +++++++ .../ssh/session/SessionStrategyFactory.kt | 20 +++ .../strategy/BasicAuthSessionStrategy.kt | 15 ++ .../strategy/SSHKeyAuthSessionStrategy.kt | 19 +++ .../ssh/session/strategy/SessionStrategy.kt | 12 ++ .../cognifide/cogboard/widget/AsyncWidget.kt | 2 +- .../cognifide/cogboard/widget/SSHWidget.kt | 7 +- 9 files changed, 154 insertions(+), 110 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SessionStrategy.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index 7ce481875..10f6cc433 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -39,12 +39,12 @@ class CogboardConstants { const val SCHEDULE_PERIOD_DEFAULT = 120L // 120 seconds const val SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds const val SSH_TIMEOUT = 5000 // 5000ms -> 5s - const val URL = "url" const val SSH_HOST = "sshAddress" - const val LOG_LINES = "logLines" - const val LOG_FILE_PATH = "logFilePath" const val SSH_KEY = "sshKey" const val SSH_KEY_PASSPHRASE = "sshKeyPassphrase" + const val URL = "url" + const val LOG_LINES = "logLines" + const val LOG_FILE_PATH = "logFilePath" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" const val USER = "user" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index e8c78f2dd..7b63393d0 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -1,7 +1,8 @@ package com.cognifide.cogboard.ssh import com.cognifide.cogboard.CogboardConstants -import com.cognifide.cogboard.ssh.auth.AuthenticationType +import com.cognifide.cogboard.ssh.auth.SSHAuthData +import com.cognifide.cogboard.ssh.session.SessionStrategyFactory import com.jcraft.jsch.ChannelExec import com.jcraft.jsch.JSch import com.jcraft.jsch.JSchException @@ -9,9 +10,9 @@ import com.jcraft.jsch.Session import io.vertx.core.AbstractVerticle import io.vertx.core.buffer.Buffer import io.vertx.core.eventbus.MessageConsumer -import io.vertx.core.json.Json -import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject +import io.vertx.core.logging.Logger +import io.vertx.core.logging.LoggerFactory import java.io.InputStream class SSHClient : AbstractVerticle() { @@ -40,103 +41,53 @@ class SSHClient : AbstractVerticle() { } } - fun tryToConnect(config: JsonObject) { + private fun tryToConnect(config: JsonObject) { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) try { connect(config) } catch (e: JSchException) { + LOGGER.error(e.message) vertx.eventBus().send(eventBusAddress, e) } } - fun connect(config: JsonObject) { - val user = config.getString(CogboardConstants.Props.USER) ?: "" - val pass = config.getString(CogboardConstants.Props.PASSWORD) ?: "" - val token = config.getString(CogboardConstants.Props.TOKEN) ?: "" - val key = config.getString(CogboardConstants.Props.SSH_KEY) ?: "" - val host = config.getString(CogboardConstants.Props.SSH_HOST) ?: "" + private fun connect(config: JsonObject) { + val authData = SSHAuthData(config) - val authTypesString = config.getString(CogboardConstants.Props.AUTHENTICATION_TYPES) - - val authTypes = if (authTypesString != null) Json.decodeValue(authTypesString) - else JsonArray() - - val authenticationType = getAuthenticationType(authTypes as JsonArray, user, token, pass, key) - - createSSHChannel(authenticationType, host, user, config) + createSSHChannel(authData) executeCommandAndSendResult(config) } - private fun createCommand(config: JsonObject): String { - val logLines = config.getString(CogboardConstants.Props.LOG_LINES) ?: "0" - val logFilePath = config.getString(CogboardConstants.Props.LOG_FILE_PATH) ?: "" - - return "cat $logFilePath | tail -$logLines" - } - - private fun createSSHChannel( - authenticationType: AuthenticationType, - host: String, - user: String, - config: JsonObject - ) { - initJsch(authenticationType, host, user, config) - val command = createCommand(config) - - if (session.isConnected) { - channel = session.openChannel("exec") as ChannelExec - - channel.setCommand(command) - channel.inputStream = null - sshInputStream = channel.inputStream + private fun createSSHChannel(authData: SSHAuthData) { + with(authData) { + initSSHSession(authData) - channel.connect(CogboardConstants.Props.SSH_TIMEOUT) + if (session.isConnected) { + createChannel(createCommand()) + } } } - private fun initJsch( - authenticationType: AuthenticationType, - host: String, - user: String, - config: JsonObject + private fun initSSHSession( + authData: SSHAuthData ) { - val securityString = getAuthenticationString(authenticationType, config) - val passphrase = config.getString(CogboardConstants.Props.SSH_KEY_PASSPHRASE) - jsch = JSch() jsch.setKnownHosts("~/.ssh/known_hosts") - when (authenticationType) { - AuthenticationType.BASIC -> { - session = jsch.getSession(user, host) - session.setPassword(securityString) - } - AuthenticationType.TOKEN -> { - session = jsch.getSession(user, host) - session.setPassword(securityString) - } - AuthenticationType.SSH_KEY -> { - if (passphrase == null) { - jsch.addIdentity(securityString) - } else { - jsch.addIdentity(securityString, passphrase) - } - session = jsch.getSession(user, host) - session.setConfig("PreferredAuthentications", "publickey") - } - } + + val session = SessionStrategyFactory(jsch).create(authData).initSession() + session.connect(CogboardConstants.Props.SSH_TIMEOUT) } - private fun getAuthenticationString( - authenticationType: AuthenticationType, - config: JsonObject - ): String { - return when (authenticationType) { - AuthenticationType.BASIC -> config.getString(CogboardConstants.Props.PASSWORD) - AuthenticationType.TOKEN -> config.getString(CogboardConstants.Props.TOKEN) - AuthenticationType.SSH_KEY -> config.getString(CogboardConstants.Props.SSH_KEY) - } + private fun createChannel(command: String) { + channel = session.openChannel("exec") as ChannelExec + + channel.setCommand(command) + channel.inputStream = null + sshInputStream = channel.inputStream + + channel.connect(CogboardConstants.Props.SSH_TIMEOUT) } private fun executeCommandAndSendResult(config: JsonObject) { @@ -150,32 +101,7 @@ class SSHClient : AbstractVerticle() { session.disconnect() } - private fun getAuthenticationType( - authenticationTypes: JsonArray, - user: String, - token: String, - pass: String, - key: String - ): AuthenticationType { - - return authenticationTypes.stream() - .map { AuthenticationType.valueOf(it.toString()) } - .filter { hasAuthTypeCorrectCredentials(it, user, token, pass, key) } - .findFirst() - .orElse(AuthenticationType.BASIC) - } - - private fun hasAuthTypeCorrectCredentials( - authType: AuthenticationType, - username: String, - token: String, - pass: String, - key: String - ): Boolean { - return when { - authType == AuthenticationType.TOKEN && username.isNotBlank() && token.isNotBlank() -> true - authType == AuthenticationType.SSH_KEY && key.isNotBlank() -> true - else -> authType == AuthenticationType.BASIC && username.isNotBlank() && pass.isNotBlank() - } + companion object { + val LOGGER: Logger = LoggerFactory.getLogger(SSHClient::class.java) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt new file mode 100644 index 000000000..ae5a36586 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -0,0 +1,49 @@ +package com.cognifide.cogboard.ssh.auth + +import com.cognifide.cogboard.CogboardConstants +import io.vertx.core.json.Json +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject + +class SSHAuthData(private val config: JsonObject) { + val user = config.getString(CogboardConstants.Props.USER) ?: "" + val password = config.getString(CogboardConstants.Props.PASSWORD) ?: "" + val token = config.getString(CogboardConstants.Props.TOKEN) ?: "" + val key = config.getString(CogboardConstants.Props.SSH_KEY) ?: "" + val host = config.getString(CogboardConstants.Props.SSH_HOST) ?: "" + val authenticationType = fromConfigAuthenticationType() + + private fun fromConfigAuthenticationType(): AuthenticationType { + val authTypesString = config.getString(CogboardConstants.Props.AUTHENTICATION_TYPES) + + val authTypes = if (authTypesString != null) Json.decodeValue(authTypesString) + else JsonArray() + + return (authTypes as JsonArray).stream() + .map { AuthenticationType.valueOf(it.toString()) } + .filter { hasAuthTypeCorrectCredentials(it) } + .findFirst() + .orElse(AuthenticationType.BASIC) + } + + private fun hasAuthTypeCorrectCredentials(authType: AuthenticationType): Boolean = + when { + authType == AuthenticationType.TOKEN && user.isNotBlank() && token.isNotBlank() -> true + authType == AuthenticationType.SSH_KEY && key.isNotBlank() -> true + else -> authType == AuthenticationType.BASIC && user.isNotBlank() && password.isNotBlank() + } + + fun getAuthenticationString(): String = + when (authenticationType) { + AuthenticationType.BASIC -> config.getString(CogboardConstants.Props.PASSWORD) + AuthenticationType.TOKEN -> config.getString(CogboardConstants.Props.TOKEN) + AuthenticationType.SSH_KEY -> config.getString(CogboardConstants.Props.SSH_KEY) + } + + fun createCommand(): String { + val logLines = config.getString(CogboardConstants.Props.LOG_LINES) ?: "0" + val logFilePath = config.getString(CogboardConstants.Props.LOG_FILE_PATH) ?: "" + + return "cat $logFilePath | tail -$logLines" + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt new file mode 100644 index 000000000..408a2cd57 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt @@ -0,0 +1,20 @@ +package com.cognifide.cogboard.ssh.session + +import com.cognifide.cogboard.ssh.auth.AuthenticationType +import com.cognifide.cogboard.ssh.auth.SSHAuthData +import com.cognifide.cogboard.ssh.session.strategy.BasicAuthSessionStrategy +import com.cognifide.cogboard.ssh.session.strategy.SSHKeyAuthSessionStrategy +import com.cognifide.cogboard.ssh.session.strategy.SessionStrategy +import com.jcraft.jsch.JSch + +class SessionStrategyFactory(private val jsch: JSch) { + fun create(authData: SSHAuthData): SessionStrategy = + when (authData.authenticationType) { + AuthenticationType.BASIC, AuthenticationType.TOKEN -> { + BasicAuthSessionStrategy(jsch, authData) + } + AuthenticationType.SSH_KEY -> { + SSHKeyAuthSessionStrategy(jsch, authData) + } + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt new file mode 100644 index 000000000..96920b28e --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt @@ -0,0 +1,15 @@ +package com.cognifide.cogboard.ssh.session.strategy + +import com.cognifide.cogboard.ssh.auth.SSHAuthData +import com.jcraft.jsch.JSch +import com.jcraft.jsch.Session + +class BasicAuthSessionStrategy(jsch: JSch, authData: SSHAuthData) : SessionStrategy(jsch, authData) { + + override fun initSession(): Session { + val session = jsch.getSession(authData.user, authData.host) + session.setPassword(securityString) + + return session + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt new file mode 100644 index 000000000..65e7c5b72 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt @@ -0,0 +1,19 @@ +package com.cognifide.cogboard.ssh.session.strategy + +import com.cognifide.cogboard.ssh.auth.SSHAuthData +import com.jcraft.jsch.JSch +import com.jcraft.jsch.Session + +class SSHKeyAuthSessionStrategy(jSch: JSch, authData: SSHAuthData) : SessionStrategy(jSch, authData) { + override fun initSession(): Session { + if (authData.password == "") { + jsch.addIdentity(securityString) + } else { + jsch.addIdentity(securityString, authData.password) + } + val session = jsch.getSession(authData.user, authData.host) + session.setConfig("PreferredAuthentications", "publickey") + + return session + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SessionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SessionStrategy.kt new file mode 100644 index 000000000..ee61acd1e --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SessionStrategy.kt @@ -0,0 +1,12 @@ +package com.cognifide.cogboard.ssh.session.strategy + +import com.cognifide.cogboard.ssh.auth.SSHAuthData +import com.jcraft.jsch.JSch +import com.jcraft.jsch.Session + +abstract class SessionStrategy(protected val jsch: JSch, protected val authData: SSHAuthData) { + protected val securityString: String + get() = authData.getAuthenticationString() + + abstract fun initSession(): Session +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt index d88fc7860..80dd5cf97 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt @@ -97,7 +97,7 @@ abstract class AsyncWidget( ) } - protected fun basicProps(url: String): JsonObject { + private fun basicProps(url: String): JsonObject { return JsonObject() .put(Props.URL, url) .put(Props.EVENT_ADDRESS, eventBusAddress) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt index 56c459c37..43939a695 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt @@ -36,6 +36,11 @@ abstract class SSHWidget( } fun sendRequestForLogs(config: JsonObject) { + ensureConfigIsPrepared(config) + vertx.eventBus().send(Event.SSH_COMMAND, config) + } + + private fun ensureConfigIsPrepared(config: JsonObject) { config.getString(Props.USER) ?: config.put(Props.USER, user) config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, password) config.getString(Props.TOKEN) ?: config.put(Props.TOKEN, token) @@ -43,7 +48,5 @@ abstract class SSHWidget( config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, host) config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, logPath) config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, logLines) - - vertx.eventBus().send(Event.SSH_COMMAND, config) } } From d82ea0a49bbd07bbc7dab61b628e01fc9a871374 Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Sun, 10 Oct 2021 22:15:36 +0200 Subject: [PATCH 022/226] More fixes after review --- .../com/cognifide/cogboard/ssh/SSHClient.kt | 13 +--------- .../cogboard/ssh/auth/SSHAuthData.kt | 24 +++++++++---------- .../ssh/session/SessionStrategyFactory.kt | 8 ++++--- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 7b63393d0..f25b0e32d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -53,49 +53,38 @@ class SSHClient : AbstractVerticle() { private fun connect(config: JsonObject) { val authData = SSHAuthData(config) - createSSHChannel(authData) - executeCommandAndSendResult(config) } private fun createSSHChannel(authData: SSHAuthData) { with(authData) { initSSHSession(authData) - if (session.isConnected) { createChannel(createCommand()) } } } - private fun initSSHSession( - authData: SSHAuthData - ) { + private fun initSSHSession(authData: SSHAuthData) { jsch = JSch() jsch.setKnownHosts("~/.ssh/known_hosts") - val session = SessionStrategyFactory(jsch).create(authData).initSession() - session.connect(CogboardConstants.Props.SSH_TIMEOUT) } private fun createChannel(command: String) { channel = session.openChannel("exec") as ChannelExec - channel.setCommand(command) channel.inputStream = null sshInputStream = channel.inputStream - channel.connect(CogboardConstants.Props.SSH_TIMEOUT) } private fun executeCommandAndSendResult(config: JsonObject) { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) - val responseBuffer = Buffer.buffer() responseBuffer.appendBytes(sshInputStream.readAllBytes()) - vertx.eventBus().send(eventBusAddress, responseBuffer) channel.disconnect() session.disconnect() diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index ae5a36586..b2f6690f6 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -1,6 +1,9 @@ package com.cognifide.cogboard.ssh.auth import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.ssh.auth.AuthenticationType.BASIC +import com.cognifide.cogboard.ssh.auth.AuthenticationType.TOKEN +import com.cognifide.cogboard.ssh.auth.AuthenticationType.SSH_KEY import io.vertx.core.json.Json import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject @@ -16,28 +19,25 @@ class SSHAuthData(private val config: JsonObject) { private fun fromConfigAuthenticationType(): AuthenticationType { val authTypesString = config.getString(CogboardConstants.Props.AUTHENTICATION_TYPES) - val authTypes = if (authTypesString != null) Json.decodeValue(authTypesString) - else JsonArray() + val authTypes = authTypesString?.let { Json.decodeValue(authTypesString) } ?: JsonArray() - return (authTypes as JsonArray).stream() + return (authTypes as JsonArray) .map { AuthenticationType.valueOf(it.toString()) } - .filter { hasAuthTypeCorrectCredentials(it) } - .findFirst() - .orElse(AuthenticationType.BASIC) + .firstOrNull { hasAuthTypeCorrectCredentials(it) } ?: BASIC } private fun hasAuthTypeCorrectCredentials(authType: AuthenticationType): Boolean = when { - authType == AuthenticationType.TOKEN && user.isNotBlank() && token.isNotBlank() -> true - authType == AuthenticationType.SSH_KEY && key.isNotBlank() -> true - else -> authType == AuthenticationType.BASIC && user.isNotBlank() && password.isNotBlank() + authType == TOKEN && user.isNotBlank() && token.isNotBlank() -> true + authType == SSH_KEY && key.isNotBlank() -> true + else -> authType == BASIC && user.isNotBlank() && password.isNotBlank() } fun getAuthenticationString(): String = when (authenticationType) { - AuthenticationType.BASIC -> config.getString(CogboardConstants.Props.PASSWORD) - AuthenticationType.TOKEN -> config.getString(CogboardConstants.Props.TOKEN) - AuthenticationType.SSH_KEY -> config.getString(CogboardConstants.Props.SSH_KEY) + BASIC -> config.getString(CogboardConstants.Props.PASSWORD) + TOKEN -> config.getString(CogboardConstants.Props.TOKEN) + SSH_KEY -> config.getString(CogboardConstants.Props.SSH_KEY) } fun createCommand(): String { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt index 408a2cd57..88b54dd2c 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt @@ -1,6 +1,8 @@ package com.cognifide.cogboard.ssh.session -import com.cognifide.cogboard.ssh.auth.AuthenticationType +import com.cognifide.cogboard.ssh.auth.AuthenticationType.BASIC +import com.cognifide.cogboard.ssh.auth.AuthenticationType.TOKEN +import com.cognifide.cogboard.ssh.auth.AuthenticationType.SSH_KEY import com.cognifide.cogboard.ssh.auth.SSHAuthData import com.cognifide.cogboard.ssh.session.strategy.BasicAuthSessionStrategy import com.cognifide.cogboard.ssh.session.strategy.SSHKeyAuthSessionStrategy @@ -10,10 +12,10 @@ import com.jcraft.jsch.JSch class SessionStrategyFactory(private val jsch: JSch) { fun create(authData: SSHAuthData): SessionStrategy = when (authData.authenticationType) { - AuthenticationType.BASIC, AuthenticationType.TOKEN -> { + BASIC, TOKEN -> { BasicAuthSessionStrategy(jsch, authData) } - AuthenticationType.SSH_KEY -> { + SSH_KEY -> { SSHKeyAuthSessionStrategy(jsch, authData) } } From ba65c41a00d5b13bc23d2bdb8fb215361db8de92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sat, 16 Oct 2021 17:53:44 +0200 Subject: [PATCH 023/226] Remove ALL log level, use INFO as default --- .../types/LogViewerWidget/Toolbar/FilterPicker/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 1e6551d1b..c999852c8 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -17,7 +17,7 @@ const FilterPicker = () => { }; const [filters, setFilters] = useState([]); - const [logLevel, setLogLevel] = useState(''); + const [logLevel, setLogLevel] = useState('info'); return ( @@ -48,7 +48,6 @@ const FilterPicker = () => { )} > - ALL DEBUG INFO WARN @@ -65,7 +64,6 @@ const FilterPicker = () => { value={logLevel} onChange={e => setLogLevel(e.target.value)} > - ALL DEBUG INFO WARN From 954f9afd8a46226af449c5ef4d02d014023f21e9 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sun, 17 Oct 2021 19:16:38 +0200 Subject: [PATCH 024/226] Added placeholder method for processing logs before sending to front-end --- .../com/cognifide/cogboard/widget/type/LogViewerWidget.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 20745f323..7ee6078db 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -25,7 +25,12 @@ class LogViewerWidget( if (checkAuthorized(responseBody)) { val logs = responseBody.getString(Props.LOG_LINES) - send(logs) + send(prepareLogs(logs)) } } + + private fun prepareLogs(logs: String): String { + // TODO + return logs + } } From 783fb34bf4dace84a0d7ee016aed9d5041b1a444 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Fri, 22 Oct 2021 13:23:48 +0200 Subject: [PATCH 025/226] Changed to using strategies [#381] --- .../cognifide/cogboard/CogboardConstants.kt | 9 +++++ .../connectionStrategy/ConnectionStrategy.kt | 5 +++ .../ConnectionStrategyFactory.kt | 16 +++++++++ .../HTTPConnectionStrategy.kt | 7 ++++ .../SSHConnectionStrategy.kt | 7 ++++ .../cogboard/widget/type/LogViewerWidget.kt | 35 ++++++++++++++++--- 6 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index 1f9433f23..c67aa805d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -40,6 +40,8 @@ class CogboardConstants { const val SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds const val URL = "url" + const val LOG_SOURCE = "logSource" + const val LOG_SOURCE_TYPE = "logSourceType" const val LOG_LINES = "logLines" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" @@ -80,6 +82,13 @@ class CogboardConstants { } } + class ConnectionType { + companion object { + const val SSH = "SSH" + const val HTTP = "HTTP" + } + } + class RequestMethod { companion object { const val GET = "get" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt new file mode 100644 index 000000000..840800555 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -0,0 +1,5 @@ +package com.cognifide.cogboard.widget.connectionStrategy + +abstract class ConnectionStrategy { + abstract fun connectAndGetResources(address: String, vararg arguments: String) +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt new file mode 100644 index 000000000..b7f6eb810 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -0,0 +1,16 @@ +package com.cognifide.cogboard.widget.connectionStrategy + +import com.cognifide.cogboard.CogboardConstants.ConnectionType +import java.lang.Exception + +class ConnectionStrategyFactory { + class ConnectionTypeException : Exception("Unknown strategy type") + + fun build(type: String): ConnectionStrategy { + return when (type) { + ConnectionType.HTTP -> HTTPConnectionStrategy() + ConnectionType.SSH -> SSHConnectionStrategy() + else -> throw ConnectionTypeException() + } + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt new file mode 100644 index 000000000..093a55691 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt @@ -0,0 +1,7 @@ +package com.cognifide.cogboard.widget.connectionStrategy + +class HTTPConnectionStrategy : ConnectionStrategy() { + override fun connectAndGetResources(address: String, vararg arguments: String) { + TODO("Not yet implemented") + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt new file mode 100644 index 000000000..cf063a0d9 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -0,0 +1,7 @@ +package com.cognifide.cogboard.widget.connectionStrategy + +class SSHConnectionStrategy : ConnectionStrategy() { + override fun connectAndGetResources(address: String, vararg arguments: String) { + TODO("Not yet implemented") + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 7ee6078db..0c629a632 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -2,26 +2,42 @@ package com.cognifide.cogboard.widget.type import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService -import com.cognifide.cogboard.widget.AsyncWidget +import com.cognifide.cogboard.widget.BaseWidget +import com.cognifide.cogboard.widget.Widget +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import io.vertx.core.Vertx +import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject class LogViewerWidget( vertx: Vertx, config: JsonObject, serv: BoardsConfigService -) : AsyncWidget(vertx, config, serv) { +) : BaseWidget(vertx, config, serv) { + private val address = config.getString(Props.LOG_SOURCE) private val lines = config.getInteger(Props.LOG_LINES) + private lateinit var consumer: MessageConsumer + private var connectionStrategy: ConnectionStrategy = determineConnectionStrategy(config) + + override fun start(): Widget { + consumer = vertx.eventBus() + .consumer(eventBusAddress) + .handler { + handleResponse(it.body()) + } + return super.start() + } + override fun updateState() { - if (url.isNotBlank()) { - httpGet("$url?lines=$lines") + if (address.isNotBlank()) { + connectionStrategy.connectAndGetResources(address = address, lines.toString()) } else { sendConfigurationError("Endpoint URL is blank") } } - override fun handleResponse(responseBody: JsonObject) { + private fun handleResponse(responseBody: JsonObject) { if (checkAuthorized(responseBody)) { val logs = responseBody.getString(Props.LOG_LINES) @@ -33,4 +49,13 @@ class LogViewerWidget( // TODO return logs } + + private fun determineConnectionStrategy(config: JsonObject): ConnectionStrategy { + // Placeholder + return object : ConnectionStrategy() { + override fun connectAndGetResources(address: String, vararg arguments: String) { + TODO("Not yet implemented") + } + } + } } From 75fff1fa2c58c80b7be662aeb2b673bac02b8987 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Fri, 22 Oct 2021 14:21:34 +0200 Subject: [PATCH 026/226] Strategy adjustments [#381] --- .../connectionStrategy/ConnectionStrategy.kt | 8 ++++++-- .../ConnectionStrategyFactory.kt | 18 ++++++++++++++++-- .../HTTPConnectionStrategy.kt | 7 +++++-- .../SSHConnectionStrategy.kt | 7 +++++-- .../cogboard/widget/type/LogViewerWidget.kt | 14 +++++++------- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index 840800555..df6726528 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -1,5 +1,9 @@ package com.cognifide.cogboard.widget.connectionStrategy -abstract class ConnectionStrategy { - abstract fun connectAndGetResources(address: String, vararg arguments: String) +import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject + +abstract class ConnectionStrategy(protected val vertx: Vertx) { + + abstract fun connectAndGetResources(address: String, arguments: JsonObject) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt index b7f6eb810..9fd11187d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -1,15 +1,29 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants.ConnectionType +import io.vertx.core.Vertx import java.lang.Exception class ConnectionStrategyFactory { class ConnectionTypeException : Exception("Unknown strategy type") + class MissingVertxException : Exception("Vertx hasn't been added to factory instance") + + private lateinit var vertx: Vertx + + fun addVertxInstance(vertx: Vertx): ConnectionStrategyFactory { + this.vertx = vertx + + return this + } fun build(type: String): ConnectionStrategy { + if (!::vertx.isInitialized) { + throw MissingVertxException() + } + return when (type) { - ConnectionType.HTTP -> HTTPConnectionStrategy() - ConnectionType.SSH -> SSHConnectionStrategy() + ConnectionType.HTTP -> HTTPConnectionStrategy(vertx) + ConnectionType.SSH -> SSHConnectionStrategy(vertx) else -> throw ConnectionTypeException() } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt index 093a55691..3d9423057 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt @@ -1,7 +1,10 @@ package com.cognifide.cogboard.widget.connectionStrategy -class HTTPConnectionStrategy : ConnectionStrategy() { - override fun connectAndGetResources(address: String, vararg arguments: String) { +import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject + +class HTTPConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { TODO("Not yet implemented") } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index cf063a0d9..2d250c359 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -1,7 +1,10 @@ package com.cognifide.cogboard.widget.connectionStrategy -class SSHConnectionStrategy : ConnectionStrategy() { - override fun connectAndGetResources(address: String, vararg arguments: String) { +import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject + +class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { TODO("Not yet implemented") } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 0c629a632..67117b02b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -5,6 +5,7 @@ import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import io.vertx.core.Vertx import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject @@ -31,7 +32,7 @@ class LogViewerWidget( override fun updateState() { if (address.isNotBlank()) { - connectionStrategy.connectAndGetResources(address = address, lines.toString()) + connectionStrategy.connectAndGetResources(address, config) } else { sendConfigurationError("Endpoint URL is blank") } @@ -51,11 +52,10 @@ class LogViewerWidget( } private fun determineConnectionStrategy(config: JsonObject): ConnectionStrategy { - // Placeholder - return object : ConnectionStrategy() { - override fun connectAndGetResources(address: String, vararg arguments: String) { - TODO("Not yet implemented") - } - } + val type = config.getString(Props.LOG_SOURCE_TYPE) + + return ConnectionStrategyFactory() + .addVertxInstance(vertx) + .build(type) } } From de0aea0be2e07f8bae0cbe76cc71317f2fdece5d Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Fri, 22 Oct 2021 23:03:23 +0200 Subject: [PATCH 027/226] Added tests and Strategies implementations [#381] --- .../cognifide/cogboard/CogboardConstants.kt | 1 + .../com/cognifide/cogboard/ssh/SSHClient.kt | 7 +- .../connectionStrategy/ConnectionStrategy.kt | 10 ++ .../ConnectionStrategyFactory.kt | 27 ++-- .../HTTPConnectionStrategy.kt | 10 -- .../SSHConnectionStrategy.kt | 21 ++- .../http/HttpConnectionStrategy.kt | 19 +++ .../http/HttpConnectionStrategyImpl.kt | 62 +++++++++ .../cogboard/widget/type/LogViewerWidget.kt | 54 ++++++-- .../cognifide/cogboard/ssh/SSHClientTest.kt | 125 ++++++++++++++++++ .../cogboard/widget/type/LogViewerTest.kt | 51 +++++++ .../com/cognifide/cogboard/ssh/id_rsa | 39 ++++++ 12 files changed, 388 insertions(+), 38 deletions(-) delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt create mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt create mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt create mode 100644 cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index 25c07c991..bba3d9236 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -46,6 +46,7 @@ class CogboardConstants { const val LOG_SOURCE = "logSource" const val LOG_SOURCE_TYPE = "logSourceType" + const val LOG_REQUEST_TYPE = "logRequestType" const val LOG_LINES = "logLines" const val LOG_FILE_PATH = "logFilePath" const val REQUEST_ID = "requestId" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index f25b0e32d..46e8467a3 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -41,7 +41,7 @@ class SSHClient : AbstractVerticle() { } } - private fun tryToConnect(config: JsonObject) { + fun tryToConnect(config: JsonObject) { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) try { connect(config) @@ -68,8 +68,9 @@ class SSHClient : AbstractVerticle() { private fun initSSHSession(authData: SSHAuthData) { jsch = JSch() - jsch.setKnownHosts("~/.ssh/known_hosts") - val session = SessionStrategyFactory(jsch).create(authData).initSession() + // jsch.setKnownHosts("~/.ssh/known_hosts") for security reasons this should be used + session = SessionStrategyFactory(jsch).create(authData).initSession() + session.setConfig("StrictHostKeyChecking", "no") // not secure session.connect(CogboardConstants.Props.SSH_TIMEOUT) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index df6726528..ae0aa6401 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -1,9 +1,19 @@ package com.cognifide.cogboard.widget.connectionStrategy +import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.http.auth.AuthenticationType import io.vertx.core.Vertx import io.vertx.core.json.JsonObject abstract class ConnectionStrategy(protected val vertx: Vertx) { + protected fun JsonObject.endpointProp(prop: String): String { + return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" + } + + protected open fun authenticationTypes(): Set { + return setOf(AuthenticationType.BASIC) + } + abstract fun connectAndGetResources(address: String, arguments: JsonObject) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt index 9fd11187d..fdc33db8a 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -1,14 +1,18 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants.ConnectionType +import com.cognifide.cogboard.widget.connectionStrategy.http.* +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.RequestMethod import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject import java.lang.Exception -class ConnectionStrategyFactory { +class ConnectionStrategyFactory(private var vertx: Vertx, props: JsonObject) { class ConnectionTypeException : Exception("Unknown strategy type") - class MissingVertxException : Exception("Vertx hasn't been added to factory instance") - private lateinit var vertx: Vertx + private val connectionType = props.getString(Props.LOG_SOURCE_TYPE) + private val requestType = props.getString(Props.LOG_REQUEST_TYPE, "") fun addVertxInstance(vertx: Vertx): ConnectionStrategyFactory { this.vertx = vertx @@ -16,13 +20,18 @@ class ConnectionStrategyFactory { return this } - fun build(type: String): ConnectionStrategy { - if (!::vertx.isInitialized) { - throw MissingVertxException() - } + fun build(): ConnectionStrategy { - return when (type) { - ConnectionType.HTTP -> HTTPConnectionStrategy(vertx) + return when (connectionType) { + ConnectionType.HTTP -> { + when (requestType) { + RequestMethod.GET -> HttpGetConnectionStrategy(vertx) + RequestMethod.PUT -> HttpPutConnectionStrategy(vertx) + RequestMethod.POST -> HttpPostConnectionStrategy(vertx) + RequestMethod.DELETE -> HttpDeleteConnectionStrategy(vertx) + else -> throw ConnectionTypeException() + } + } ConnectionType.SSH -> SSHConnectionStrategy(vertx) else -> throw ConnectionTypeException() } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt deleted file mode 100644 index 3d9423057..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.cognifide.cogboard.widget.connectionStrategy - -import io.vertx.core.Vertx -import io.vertx.core.json.JsonObject - -class HTTPConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - TODO("Not yet implemented") - } -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index 2d250c359..e4b3a1d08 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -1,10 +1,27 @@ package com.cognifide.cogboard.widget.connectionStrategy +import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.CogboardConstants.Props import io.vertx.core.Vertx +import io.vertx.core.json.Json import io.vertx.core.json.JsonObject -class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { +open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { override fun connectAndGetResources(address: String, arguments: JsonObject) { - TODO("Not yet implemented") + val config = ensureConfigIsPrepared(arguments) + vertx.eventBus().send(CogboardConstants.Event.SSH_COMMAND, config) + } + + private fun ensureConfigIsPrepared(config: JsonObject): JsonObject { + config.getString(Props.USER) ?: config.put(Props.USER, config.endpointProp(Props.USER)) + config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, config.endpointProp(Props.PASSWORD)) + config.getString(Props.TOKEN) ?: config.put(Props.TOKEN, config.endpointProp(Props.TOKEN)) + config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, config.endpointProp(Props.SSH_KEY)) + config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, config.endpointProp(Props.SSH_HOST)) + config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, config.endpointProp(Props.LOG_FILE_PATH)) + config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, config.endpointProp(Props.LOG_LINES)) + config.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + + return config } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt new file mode 100644 index 000000000..e5a568a2a --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt @@ -0,0 +1,19 @@ +package com.cognifide.cogboard.widget.connectionStrategy.http + +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy +import io.vertx.core.Vertx +import io.vertx.core.json.Json +import io.vertx.core.json.JsonObject + +abstract class HttpConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { + protected open fun basicProps(props: JsonObject): JsonObject { + return JsonObject() + .put(Props.URL, props.endpointProp(Props.URL)) + .put(Props.EVENT_ADDRESS, props.endpointProp(Props.EVENT_ADDRESS)) + .put(Props.USER, props.endpointProp(Props.USER)) + .put(Props.PASSWORD, props.endpointProp(Props.PASSWORD)) + .put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + .put(Props.CONTENT_TYPE, props.endpointProp(Props.CONTENT_TYPE)) + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt new file mode 100644 index 000000000..3cfca3969 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt @@ -0,0 +1,62 @@ +package com.cognifide.cogboard.widget.connectionStrategy.http + +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.Event +import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject + +class HttpGetConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { + vertx.eventBus().send(Event.HTTP_GET, + basicProps(arguments) + ) + } + + override fun basicProps(props: JsonObject): JsonObject { + return super.basicProps(props) + .put(Props.REQUEST_ID, props.getValue(Props.REQUEST_ID, "")) + .put(Props.TOKEN, props.endpointProp(Props.TOKEN)) + } +} + +class HttpGetStatusConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { + vertx.eventBus().send(Event.HTTP_CHECK, + basicProps(arguments) + ) + } +} + +class HttpPutConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { + vertx.eventBus().send(Event.HTTP_PUT, + basicProps(arguments) + ) + } + + override fun basicProps(props: JsonObject): JsonObject { + return super.basicProps(props) + .put(Props.BODY, props.getJsonObject(Props.BODY)) + } +} + +class HttpPostConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { + vertx.eventBus().send(Event.HTTP_POST, + basicProps(arguments) + ) + } + + override fun basicProps(props: JsonObject): JsonObject { + return super.basicProps(props) + .put(Props.BODY, props.getJsonObject(Props.BODY)) + } +} + +class HttpDeleteConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { + vertx.eventBus().send(Event.HTTP_DELETE, + basicProps(arguments) + ) + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 67117b02b..acf22e5be 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -1,14 +1,17 @@ package com.cognifide.cogboard.widget.type import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.ConnectionType import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import io.vertx.core.Vertx +import io.vertx.core.buffer.Buffer import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject +import java.nio.charset.Charset class LogViewerWidget( vertx: Vertx, @@ -16,20 +19,38 @@ class LogViewerWidget( serv: BoardsConfigService ) : BaseWidget(vertx, config, serv) { private val address = config.getString(Props.LOG_SOURCE) - private val lines = config.getInteger(Props.LOG_LINES) + private val lines = config.getString(Props.LOG_LINES) + private val connectionType = config.getString(Props.LOG_SOURCE_TYPE) - private lateinit var consumer: MessageConsumer - private var connectionStrategy: ConnectionStrategy = determineConnectionStrategy(config) + private var consumer: MessageConsumer<*>? = null + var connectionStrategy: ConnectionStrategy = determineConnectionStrategy() override fun start(): Widget { - consumer = vertx.eventBus() - .consumer(eventBusAddress) - .handler { - handleResponse(it.body()) - } + when (connectionType) { + ConnectionType.SSH -> { + consumer = vertx.eventBus() + .consumer(eventBusAddress) { + handleResponse(it.body()) + } + } + ConnectionType.HTTP -> { + consumer = vertx.eventBus() + .consumer(eventBusAddress) { + handleResponse(it.body()) + } + } + else -> { + sendConfigurationError("No type of connection chosen") + } + } return super.start() } + override fun stop(): Widget { + consumer?.unregister() + return super.stop() + } + override fun updateState() { if (address.isNotBlank()) { connectionStrategy.connectAndGetResources(address, config) @@ -46,16 +67,21 @@ class LogViewerWidget( } } + private fun handleResponse(responseBody: Buffer) { + val logs = prepareLogs( + responseBody.toString(Charset.defaultCharset()) + ) + + send(logs) + } + private fun prepareLogs(logs: String): String { // TODO return logs } - private fun determineConnectionStrategy(config: JsonObject): ConnectionStrategy { - val type = config.getString(Props.LOG_SOURCE_TYPE) - - return ConnectionStrategyFactory() + private fun determineConnectionStrategy() = + ConnectionStrategyFactory(vertx, config) .addVertxInstance(vertx) - .build(type) - } + .build() } diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt new file mode 100644 index 000000000..2c2605577 --- /dev/null +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt @@ -0,0 +1,125 @@ +package com.cognifide.cogboard.ssh + +import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.ssh.auth.AuthenticationType +import com.jcraft.jsch.JSchException +import io.vertx.core.Vertx +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.EventBus +import io.vertx.core.json.Json +import io.vertx.core.json.JsonObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.* +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations.initMocks +import org.mockito.junit.jupiter.MockitoExtension + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(MockitoExtension::class) +class SSHClientTest { + private lateinit var sshClient: SSHClient + + @Captor + private lateinit var bufferCaptor: ArgumentCaptor + + @Captor + private lateinit var exceptionCaptor: ArgumentCaptor + + private lateinit var config: JsonObject + + @Mock + lateinit var vertx: Vertx + + @Mock + lateinit var eventBus: EventBus + + @BeforeEach + fun init() { + sshClient = SSHClient() + initMocks(this) + `when`(vertx.eventBus()).thenReturn(eventBus) + sshClient.init(vertx, null) + } + + private fun generateConfig(authTypes: Set) { + config = JsonObject() + .put(CogboardConstants.Props.USER, "mock") + .put(CogboardConstants.Props.PASSWORD, "TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E") + .put(CogboardConstants.Props.TOKEN, "TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E") + .put(CogboardConstants.Props.SSH_KEY, SSHClientTest::class.java.getResource( + "/com/cognifide/cogboard/ssh/id_rsa").path) + .put(CogboardConstants.Props.SSH_HOST, "150.254.30.120") + .put(CogboardConstants.Props.LOG_FILE_PATH, "/home/mock/example.txt") + .put(CogboardConstants.Props.LOG_LINES, "1") + .put(CogboardConstants.Props.AUTHENTICATION_TYPES, Json.encode(authTypes)) + .put(CogboardConstants.Props.EVENT_ADDRESS, "ssh.test.address") + } + + @Test + fun `Executing commands with user+password authentication succeeds`() { + generateConfig(setOf(AuthenticationType.BASIC)) + + sshClient.tryToConnect(config) + val (result, exception) = captureWhatIsSent(eventBus, bufferCaptor, exceptionCaptor) + + result?.let { + assertEquals("19:28:11.445 [vert.x-eventloop-thread-2] " + + "ERROR c.cognifide.cogboard.http.HttpClient - Connection was closed\n", + it.getString(0, it.length()) + ) + } + assert(exception == null) + } + + @Test + fun `Executing commands using key authentication succeeds`() { + generateConfig(setOf(AuthenticationType.SSH_KEY)) + + sshClient.tryToConnect(config) + val (result, exception) = captureWhatIsSent(eventBus, bufferCaptor, exceptionCaptor) + + result?.let { + assertEquals("19:28:11.445 [vert.x-eventloop-thread-2] " + + "ERROR c.cognifide.cogboard.http.HttpClient - Connection was closed\n", + it.getString(0, it.length()) + ) + } + assert(exception == null) + } + + @Test + fun `Executing commands with wrong credentials fails`() { + generateConfig(setOf(AuthenticationType.BASIC)) + config.remove(CogboardConstants.Props.PASSWORD) + config.put(CogboardConstants.Props.PASSWORD, "wrong") + + sshClient.tryToConnect(config) + + val (result, exception) = captureWhatIsSent(eventBus, bufferCaptor, exceptionCaptor) + + assert(result == null) + assert(exception is JSchException) + } + + private fun captureWhatIsSent(eventBus: EventBus, + bufferCaptor: ArgumentCaptor, + exceptionCaptor: ArgumentCaptor + ): Pair{ + return try { + verify(eventBus).send(eq("ssh.test.address"), bufferCaptor.capture()) + verify(eventBus, times(0)).send(eq("ssh.test.address"), any(Exception::class.java)) + Pair(bufferCaptor.value, null) + } catch (e: Throwable) { + verify(eventBus, times(0)).send(eq("ssh.test.address"), any(Buffer::class.java)) + verify(eventBus).send(eq("ssh.test.address"), exceptionCaptor.capture()) + Pair(null, exceptionCaptor.value) + } + } +} \ No newline at end of file diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt new file mode 100644 index 000000000..8d875706a --- /dev/null +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt @@ -0,0 +1,51 @@ +package com.cognifide.cogboard.widget.type + +import com.cognifide.cogboard.CogboardConstants.ConnectionType +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.RequestMethod +import io.vertx.core.buffer.Buffer +import io.vertx.core.json.JsonObject +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* + +class LogViewerTest: WidgetTestBase() { + + private lateinit var widget: LogViewerWidget + + override fun widgetName(): String { + return "LogViewerWidget" + } + + @BeforeEach + fun initForTest() { + super.init() + } + + @Test + fun `Expect Buffer consumer to be used when type is SSH`() { + val config = initWidget() + .put(Props.LOG_SOURCE_TYPE, ConnectionType.SSH) + .put(Props.LOG_SOURCE, "192.168.0.1") + .put(Props.LOG_LINES, "5") + widget = LogViewerWidget(vertx, config, initService()) + + widget.start() + + verify(eventBus).consumer(eq(widget.eventBusAddress), any()) + } + + @Test + fun `Expect JsonObject consumer to be used when type is HTTP`() { + val config = initWidget() + .put(Props.LOG_SOURCE_TYPE, ConnectionType.HTTP) + .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) + .put(Props.LOG_SOURCE, "192.168.0.1") + .put(Props.LOG_LINES, "5") + widget = LogViewerWidget(vertx, config, initService()) + + widget.start() + + verify(eventBus).consumer(eq(widget.eventBusAddress), any()) + } +} \ No newline at end of file diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa b/cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa new file mode 100644 index 000000000..2d77f8a17 --- /dev/null +++ b/cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG5AIBAAKCAYEApphpbfnbEh4ZFdnloNqz0P3IDnADldRaD9ATzskGdcdRebJX +7mRJ+f174wyfee8WUl5W74TNIMHOo2oHFLD+/qtSg8m+w5JttmTS02wVCw/vBgC/ +3WSrR71jScBTQkm34ZQW9cZvuB0iJO3OtnGS2PJSxe1E8UNsey7EP/oXWjomCCgx +50hgWWDdj4PNHyMQoOcq9bZWT1yjWppSuBgaVYKwZhZeUPrObbixKOxMKqxEqjul +ayepln3DyI2Mq+/darE2cKD3FBA9MI6rbyA6nf0ncWCrrPsv1TpLFzqP9q1T+zPP +igpI778BuVJUYEEOWWmPmquti1tkiiAwJzKTaDILDObfifgcd9eyyr4FzZoArwep +Un6+yECthy2VMhxbGsJXVAnYmC3/06MFVjNXLgTvQG/h2YsknkSKaBgVdqI5ireK +l1rxkmIBzjdqJ8QuJjR1TbSxjmsDNeNCxrskLfAL3ATRJGmdXQHv6GU1g3Lt8qjr +TF/MMdV//oM/NwxfAgMBAAECggGAS/PaxVgPh8APIcY5CdAMATFi3yo0iD/LW8A6 +96DxAAuCWuBzdG1myMHHlKlxn87gN2dpbUP3nYKeqiQx3D4h04vaT9lPzqxYtrpa +4Cc7pNJ74BnBX7eA23E97ibwDp27Zu30VdNFpgQqR/nfr5AyKhS4piJZt0FNGRAm +SyDDOtCd/EQPFGoL+1PNT5wAjbyX3TPngyTBTudmVm1bXzEl+Gxf7fsuvu7J5H46 +zvSEeUQW/iG5dhQcHk9yTmIyi0UMyipOv+e+K4ujPQYDTaGL1y2czUlnCGZkztql +DWVRI54YBfNDX0tawaPrIuL7BT1yZVGaT3w89FIckJqRNBCy9LeaSz3qphpuLtZ6 +jZLGByEIurQCGJHhcJfU/mm25uCc3FFiSWeCIQcIMgF0zlQtlKFBsLzIZrHBFY6Q +XL4sxS6/QpotQ4p2tF1RbG2e6RjnJzammk79U5hs7/9nb4IbSQcn0SDz9UpNQzOD +WENMMQbUtyz4JEWrQPfNpgGdyOlxAoHBANVgiUrX27l6f9yUYdET4LCA6yINVQM5 +628+78Ho1GrnZqs3F+bO+9+jjGgW1ousLsnbmeaLNvbm2tZJhKCy4SfwypYCZDpO +8QY1Pscg6Wd8gsrjyjq6j/FoVp9TrWs4tUHjbIv9YEtBXdeto2CCSb/uhlc6jUDj +tmY0OLf5fWKFrKmfIZSFT/mz8ZO5KFXILuO395ZG/tMLxsRVm1Rsj2xFzUOnt6hg +NEJxI8Qx/7MC69SNrdkt34Kj7JdfkXeJmQKBwQDH35kbAMExA+Gznm/eE89TyDLc +P5qbsP62rc9BqU8aTnwwsd2QGhU3qwGx/0TEL+mR77j7cyXs8Z8bhqoqjr/5IvkJ +Bk8D18bNmu+QCeRs+kDUf1XdUzCF5uH6QezpnntD7KnUf+aJ+Byf6qp0WdLTyi/m +eJq2OqMqeDJnapY7wC9FpycD2Qw3eh47CFfIPFb+cRjsmIPaG28PHSmcU+uvZy9Z +t5wo7z+AmkBvcBBlPGORmH/4EUbAzNitCLEPMLcCgcAYngeY6+h8sKZZw4C5h2qP +0n/OMO2S7ra74e31WDBRmRreO//08AJdHdhQFfpwqM/JGB3cXEleGOo8eMTlaV3S +ptQolGQN2heymKgCf0iOWO4aDEfDmMyMlHYR8ax7R1KaJGvchsH6TLNP88d6le1+ +SD2ViLrNNrhPeEzm5NDTcgqmkyZjpTHB5EHFwkHmC69lNE9LNiumG6C1/A5sog21 +9yOkX5Avy9GbrMPsmbwfYgHlRuc8vC8iOllMaYk12lkCgcEAnrqVznoE8rYQciLq +2ESHLBQbQApK8EXZop3ZsF2S8mNOASXfvObZ3sGxsPf2Vo9uQHVORHKntcoU/zks +vmtrTaFWgLI/4d9khDf89DjG7Ty4a4lMWV4NdOlsoN/mE4nGfqTeImIIZZ1iriUg +hRIoegVhkPhKdVmP5tRZwwNEl+MCJ6CdPVMLfqs+gscXX2977slGb+6XF4kd47TX +LM0AqOWbFvbXzzJhDfJyJY2rl5ojwKaJSin+NHL5yEDUzCl1AoHBAK85ZTR0CmlE +jV4OrsBk937oDysYMYH6IYAUQ4xX4jCu5nbHi6xIpAuha2Xh0vWCr7xoguxDE67j +z6NeE2s3wsjK5I9tvAxwvpnSUvMPFVojlxrPQLHcRh3VmAq3kYtF2HrmJx+lsmDD +mLZLHgj4lqcWlhME4ZsENrLBoUtwx9NEvTjJoNGdeU1TsDSUx/a5DGleeqLHjejS +SoGobi0iGwCCe/gfAyJ4zgtYe3ie6yJRcsESHuCfrk6ET0vodep/nA== +-----END RSA PRIVATE KEY----- From 1dbb996503b8f6d048a62c6ae0218957f4b09102 Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Fri, 22 Oct 2021 23:08:33 +0200 Subject: [PATCH 028/226] Deleted SSHWidget - replaced by strategies [#381] --- .../cognifide/cogboard/widget/SSHWidget.kt | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt deleted file mode 100644 index 43939a695..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.cognifide.cogboard.widget - -import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.CogboardConstants.Event -import com.cognifide.cogboard.config.service.BoardsConfigService -import io.vertx.core.Vertx -import io.vertx.core.eventbus.MessageConsumer -import io.vertx.core.json.JsonObject -import java.nio.Buffer - -abstract class SSHWidget( - vertx: Vertx, - config: JsonObject, - serv: BoardsConfigService -) : AsyncWidget(vertx, config, serv) { - val sshKey: String = config.endpointProp(Props.SSH_KEY) - val host: String = config.endpointProp(Props.SSH_HOST) - val logPath: String = config.endpointProp(Props.LOG_FILE_PATH) - val logLines: String = config.endpointProp(Props.LOG_LINES) - private lateinit var sshConsumer: MessageConsumer - - fun registerForSSH(eventBusAddress: String) { - sshConsumer = vertx.eventBus() - .consumer(eventBusAddress) - .handler { - handleSSHResponse(it.body()) - } - } - - abstract fun handleSSHResponse(body: Buffer?) - - fun unregisterFromSSH() { - if (::sshConsumer.isInitialized) { - sshConsumer.unregister() - } - } - - fun sendRequestForLogs(config: JsonObject) { - ensureConfigIsPrepared(config) - vertx.eventBus().send(Event.SSH_COMMAND, config) - } - - private fun ensureConfigIsPrepared(config: JsonObject) { - config.getString(Props.USER) ?: config.put(Props.USER, user) - config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, password) - config.getString(Props.TOKEN) ?: config.put(Props.TOKEN, token) - config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, sshKey) - config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, host) - config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, logPath) - config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, logLines) - } -} From 23a2b6b8bc2fac0885559d77717617cf6f6596dc Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Sat, 23 Oct 2021 20:33:17 +0200 Subject: [PATCH 029/226] Changes after code review - problem with tests to solve --- api-mocks/__files/logViewer/logs.json | 10 ++- api-mocks/mappings/endpoints-mapping.json | 2 +- .../cognifide/cogboard/widget/WidgetIndex.kt | 15 ++++- .../connectionStrategy/ConnectionStrategy.kt | 6 +- .../ConnectionStrategyFactory.kt | 28 +++------ .../HttpConnectionStrategy.kt | 51 +++++++++++++++ .../SSHConnectionStrategy.kt | 10 ++- .../http/HttpConnectionStrategy.kt | 19 ------ .../http/HttpConnectionStrategyImpl.kt | 62 ------------------- .../cogboard/widget/type/LogViewerWidget.kt | 39 +++++------- 10 files changed, 115 insertions(+), 127 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt diff --git a/api-mocks/__files/logViewer/logs.json b/api-mocks/__files/logViewer/logs.json index b4211a0e3..e5c4e7400 100644 --- a/api-mocks/__files/logViewer/logs.json +++ b/api-mocks/__files/logViewer/logs.json @@ -1,3 +1,11 @@ { - "logLines": "02:49:12 127.0.0.1 GET / 200\n02:49:35 127.0.0.1 GET /index.html 200\n03:01:06 127.0.0.1 GET /images/sponsered.gif 304\n03:52:36 127.0.0.1 GET /search.php 200\n04:17:03 127.0.0.1 GET /admin/style.css 200\n05:04:54 127.0.0.1 GET /favicon.ico 404\n05:38:07 127.0.0.1 GET /js/ads.js 200\n" + "logLines": [ + "02:49:12 127.0.0.1 GET / 200", + "02:49:35 127.0.0.1 GET /index.html 200", + "03:01:06 127.0.0.1 GET /images/sponsered.gif 304", + "03:52:36 127.0.0.1 GET /search.php 200", + "04:17:03 127.0.0.1 GET /admin/style.css 200", + "05:04:54 127.0.0.1 GET /favicon.ico 404", + "05:38:07 127.0.0.1 GET /js/ads.js 200" + ] } \ No newline at end of file diff --git a/api-mocks/mappings/endpoints-mapping.json b/api-mocks/mappings/endpoints-mapping.json index 8dc85fa63..d98b991d2 100644 --- a/api-mocks/mappings/endpoints-mapping.json +++ b/api-mocks/mappings/endpoints-mapping.json @@ -3,7 +3,7 @@ { "request": { "method": "GET", - "url": "/logviewer", + "url": "/log-viewer", "queryParameters": { "lines": { "matches": "^[0-9]+$" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt index a73e9feb6..e057a29be 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt @@ -3,7 +3,20 @@ package com.cognifide.cogboard.widget import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.storage.VolumeStorageFactory.appConfig -import com.cognifide.cogboard.widget.type.* +import com.cognifide.cogboard.widget.type.AemBundleInfoWidget +import com.cognifide.cogboard.widget.type.AemHealthcheckWidget +import com.cognifide.cogboard.widget.type.BambooDeploymentWidget +import com.cognifide.cogboard.widget.type.BambooPlanWidget +import com.cognifide.cogboard.widget.type.CheckboxWidget +import com.cognifide.cogboard.widget.type.IframeEmbedWidget +import com.cognifide.cogboard.widget.type.JenkinsJobWidget +import com.cognifide.cogboard.widget.type.JiraBucketsWidget +import com.cognifide.cogboard.widget.type.LinkListWidget +import com.cognifide.cogboard.widget.type.WhiteSpaceWidget +import com.cognifide.cogboard.widget.type.ServiceCheckWidget +import com.cognifide.cogboard.widget.type.TextWidget +import com.cognifide.cogboard.widget.type.ToDoListWidget +import com.cognifide.cogboard.widget.type.WorldClockWidget import com.cognifide.cogboard.widget.type.randompicker.RandomPickerWidget import com.cognifide.cogboard.widget.type.sonarqube.SonarQubeWidget import com.cognifide.cogboard.widget.type.zabbix.ZabbixWidget diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index ae0aa6401..74f1025ea 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -3,10 +3,10 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.http.auth.AuthenticationType import io.vertx.core.Vertx +import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject abstract class ConnectionStrategy(protected val vertx: Vertx) { - protected fun JsonObject.endpointProp(prop: String): String { return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" } @@ -16,4 +16,8 @@ abstract class ConnectionStrategy(protected val vertx: Vertx) { } abstract fun connectAndGetResources(address: String, arguments: JsonObject) + + abstract fun getConsumer(eventBusAddress: String): MessageConsumer<*> + + abstract fun handleResponse(response: Any): String } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt index fdc33db8a..d728ff155 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -1,39 +1,31 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants.ConnectionType -import com.cognifide.cogboard.widget.connectionStrategy.http.* import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.CogboardConstants.RequestMethod import io.vertx.core.Vertx import io.vertx.core.json.JsonObject -import java.lang.Exception -class ConnectionStrategyFactory(private var vertx: Vertx, props: JsonObject) { - class ConnectionTypeException : Exception("Unknown strategy type") +class UnknownConnectionTypeException( + message: String? +) : RuntimeException(message) + +class ConnectionStrategyFactory( + private var vertx: Vertx, + props: JsonObject +) { private val connectionType = props.getString(Props.LOG_SOURCE_TYPE) - private val requestType = props.getString(Props.LOG_REQUEST_TYPE, "") fun addVertxInstance(vertx: Vertx): ConnectionStrategyFactory { this.vertx = vertx - return this } fun build(): ConnectionStrategy { - return when (connectionType) { - ConnectionType.HTTP -> { - when (requestType) { - RequestMethod.GET -> HttpGetConnectionStrategy(vertx) - RequestMethod.PUT -> HttpPutConnectionStrategy(vertx) - RequestMethod.POST -> HttpPostConnectionStrategy(vertx) - RequestMethod.DELETE -> HttpDeleteConnectionStrategy(vertx) - else -> throw ConnectionTypeException() - } - } + ConnectionType.HTTP -> HttpConnectionStrategy(vertx) ConnectionType.SSH -> SSHConnectionStrategy(vertx) - else -> throw ConnectionTypeException() + else -> throw UnknownConnectionTypeException("Unknown strategy type") } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt new file mode 100644 index 000000000..a5728fe49 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt @@ -0,0 +1,51 @@ +package com.cognifide.cogboard.widget.connectionStrategy + +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.Event +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.GET +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.PUT +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.POST +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.DELETE +import io.vertx.core.Vertx +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.Json +import io.vertx.core.json.JsonObject + +class HttpConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { + when (arguments.getString(Props.LOG_REQUEST_TYPE, "")) { + GET -> vertx.eventBus().send(Event.HTTP_GET, getProps(arguments)) + PUT -> vertx.eventBus().send(Event.HTTP_PUT, putProps(arguments)) + POST -> vertx.eventBus().send(Event.HTTP_POST, postProps(arguments)) + DELETE -> vertx.eventBus().send(Event.HTTP_DELETE, basicProps(arguments)) + } + } + + override fun getConsumer(eventBusAddress: String): MessageConsumer<*> = + vertx.eventBus().consumer(eventBusAddress) + + override fun handleResponse(response: Any): String = + (response as JsonObject).getString(Props.LOG_LINES) + + private fun basicProps(props: JsonObject): JsonObject = + JsonObject() + .put(Props.URL, props.endpointProp(Props.URL)) + .put(Props.EVENT_ADDRESS, props.endpointProp(Props.EVENT_ADDRESS)) + .put(Props.USER, props.endpointProp(Props.USER)) + .put(Props.PASSWORD, props.endpointProp(Props.PASSWORD)) + .put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + .put(Props.CONTENT_TYPE, props.endpointProp(Props.CONTENT_TYPE)) + + private fun getProps(props: JsonObject): JsonObject = + basicProps(props) + .put(Props.REQUEST_ID, props.getValue(Props.REQUEST_ID, "")) + .put(Props.TOKEN, props.endpointProp(Props.TOKEN)) + + private fun putProps(props: JsonObject): JsonObject = + basicProps(props) + .put(Props.BODY, props.getJsonObject(Props.BODY)) + + private fun postProps(props: JsonObject): JsonObject = + basicProps(props) + .put(Props.BODY, props.getJsonObject(Props.BODY)) +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index e4b3a1d08..13f59c16b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -3,8 +3,11 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.CogboardConstants.Props import io.vertx.core.Vertx +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.Json import io.vertx.core.json.JsonObject +import java.nio.charset.Charset open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { override fun connectAndGetResources(address: String, arguments: JsonObject) { @@ -12,6 +15,12 @@ open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { vertx.eventBus().send(CogboardConstants.Event.SSH_COMMAND, config) } + override fun getConsumer(eventBusAddress: String): MessageConsumer<*> = + vertx.eventBus().consumer(eventBusAddress) + + override fun handleResponse(response: Any): String = + (response as Buffer).toString(Charset.defaultCharset()) + private fun ensureConfigIsPrepared(config: JsonObject): JsonObject { config.getString(Props.USER) ?: config.put(Props.USER, config.endpointProp(Props.USER)) config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, config.endpointProp(Props.PASSWORD)) @@ -21,7 +30,6 @@ open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, config.endpointProp(Props.LOG_FILE_PATH)) config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, config.endpointProp(Props.LOG_LINES)) config.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) - return config } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt deleted file mode 100644 index e5a568a2a..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategy.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.cognifide.cogboard.widget.connectionStrategy.http - -import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy -import io.vertx.core.Vertx -import io.vertx.core.json.Json -import io.vertx.core.json.JsonObject - -abstract class HttpConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { - protected open fun basicProps(props: JsonObject): JsonObject { - return JsonObject() - .put(Props.URL, props.endpointProp(Props.URL)) - .put(Props.EVENT_ADDRESS, props.endpointProp(Props.EVENT_ADDRESS)) - .put(Props.USER, props.endpointProp(Props.USER)) - .put(Props.PASSWORD, props.endpointProp(Props.PASSWORD)) - .put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) - .put(Props.CONTENT_TYPE, props.endpointProp(Props.CONTENT_TYPE)) - } -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt deleted file mode 100644 index 3cfca3969..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/http/HttpConnectionStrategyImpl.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.cognifide.cogboard.widget.connectionStrategy.http - -import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.CogboardConstants.Event -import io.vertx.core.Vertx -import io.vertx.core.json.JsonObject - -class HttpGetConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - vertx.eventBus().send(Event.HTTP_GET, - basicProps(arguments) - ) - } - - override fun basicProps(props: JsonObject): JsonObject { - return super.basicProps(props) - .put(Props.REQUEST_ID, props.getValue(Props.REQUEST_ID, "")) - .put(Props.TOKEN, props.endpointProp(Props.TOKEN)) - } -} - -class HttpGetStatusConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - vertx.eventBus().send(Event.HTTP_CHECK, - basicProps(arguments) - ) - } -} - -class HttpPutConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - vertx.eventBus().send(Event.HTTP_PUT, - basicProps(arguments) - ) - } - - override fun basicProps(props: JsonObject): JsonObject { - return super.basicProps(props) - .put(Props.BODY, props.getJsonObject(Props.BODY)) - } -} - -class HttpPostConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - vertx.eventBus().send(Event.HTTP_POST, - basicProps(arguments) - ) - } - - override fun basicProps(props: JsonObject): JsonObject { - return super.basicProps(props) - .put(Props.BODY, props.getJsonObject(Props.BODY)) - } -} - -class HttpDeleteConnectionStrategy(vertx: Vertx) : HttpConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - vertx.eventBus().send(Event.HTTP_DELETE, - basicProps(arguments) - ) - } -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index acf22e5be..9b08fd40d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -1,7 +1,6 @@ package com.cognifide.cogboard.widget.type import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.CogboardConstants.ConnectionType import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget @@ -9,6 +8,7 @@ import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import io.vertx.core.Vertx import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject import java.nio.charset.Charset @@ -23,25 +23,12 @@ class LogViewerWidget( private val connectionType = config.getString(Props.LOG_SOURCE_TYPE) private var consumer: MessageConsumer<*>? = null - var connectionStrategy: ConnectionStrategy = determineConnectionStrategy() + private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() override fun start(): Widget { - when (connectionType) { - ConnectionType.SSH -> { - consumer = vertx.eventBus() - .consumer(eventBusAddress) { - handleResponse(it.body()) - } - } - ConnectionType.HTTP -> { - consumer = vertx.eventBus() - .consumer(eventBusAddress) { - handleResponse(it.body()) - } - } - else -> { - sendConfigurationError("No type of connection chosen") - } + consumer = connectionStrategy.getConsumer(eventBusAddress) + consumer!!.handler { + handleResponse(it) } return super.start() } @@ -59,19 +46,25 @@ class LogViewerWidget( } } + private fun handleResponse(response: Message<*>) { + val responseBody = response.body() + if (responseBody is JsonObject) { + if (checkAuthorized(responseBody)) { + send(prepareLogs(connectionStrategy.handleResponse(responseBody))) + } + } else { + send(prepareLogs(connectionStrategy.handleResponse(responseBody))) + } + } private fun handleResponse(responseBody: JsonObject) { if (checkAuthorized(responseBody)) { val logs = responseBody.getString(Props.LOG_LINES) - send(prepareLogs(logs)) } } private fun handleResponse(responseBody: Buffer) { - val logs = prepareLogs( - responseBody.toString(Charset.defaultCharset()) - ) - + val logs = prepareLogs(responseBody.toString(Charset.defaultCharset())) send(logs) } From 32391535f6a7042fee220cf2d3a1171a650246cc Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 23 Oct 2021 21:31:25 +0200 Subject: [PATCH 030/226] Add ssh to endpoints dialog fields #412 --- cogboard-webapp/src/components/widgets/dialogFields/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index 2ec23c533..33cad5104 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -105,7 +105,7 @@ const dialogFields = { name: 'publicUrl', label: 'Public URL', validator: () => - string().matches(/^(http|https|ws|ftp):\/\/.*([:.]).*/, { + string().matches(/^(http|https|ws|ftp|ssh):\/\/.*([:.]).*/, { message: vm.INVALID_PUBLIC_URL(), excludeEmptyString: true }) @@ -251,7 +251,7 @@ const dialogFields = { name: 'url', label: 'URL', validator: () => - string().matches(/^(http|https|ws|ftp):\/\/.*([:.]).*/, { + string().matches(/^(http|https|ws|ftp|ssh):\/\/.*([:.]).*/, { message: vm.INVALID_URL(), excludeEmptyString: true }) From 26a173e657ab5296e4b29bb9ec2baf0b8fc027ed Mon Sep 17 00:00:00 2001 From: Michal Przypasniak Date: Sat, 23 Oct 2021 23:18:22 +0200 Subject: [PATCH 031/226] Tests fixed --- .../cognifide/cogboard/widget/type/LogViewerTest.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt index 8d875706a..896b60b27 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt @@ -4,6 +4,7 @@ import com.cognifide.cogboard.CogboardConstants.ConnectionType import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.CogboardConstants.RequestMethod import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -24,6 +25,9 @@ class LogViewerTest: WidgetTestBase() { @Test fun `Expect Buffer consumer to be used when type is SSH`() { + val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer + `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + val config = initWidget() .put(Props.LOG_SOURCE_TYPE, ConnectionType.SSH) .put(Props.LOG_SOURCE, "192.168.0.1") @@ -32,11 +36,14 @@ class LogViewerTest: WidgetTestBase() { widget.start() - verify(eventBus).consumer(eq(widget.eventBusAddress), any()) + verify(eventBus).consumer(eq(widget.eventBusAddress)) } @Test fun `Expect JsonObject consumer to be used when type is HTTP`() { + val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer + `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + val config = initWidget() .put(Props.LOG_SOURCE_TYPE, ConnectionType.HTTP) .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) @@ -46,6 +53,6 @@ class LogViewerTest: WidgetTestBase() { widget.start() - verify(eventBus).consumer(eq(widget.eventBusAddress), any()) + verify(eventBus).consumer(eq(widget.eventBusAddress)) } } \ No newline at end of file From 737c288e373d5c9d0c1c2b15cec683704ed1ceb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 24 Oct 2021 16:27:19 +0200 Subject: [PATCH 032/226] Add SSH Key field to Credentials --- .../config/controller/CredentialsController.kt | 1 + .../cognifide/cogboard/config/model/Credential.kt | 3 ++- .../src/components/CredentialForm/index.js | 6 ++++-- .../src/components/widgets/dialogFields/index.js | 15 +++++++++++++++ cogboard-webapp/src/constants/index.js | 5 ++++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt index b417d92b6..d2cdc3796 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt @@ -37,6 +37,7 @@ class CredentialsController : AbstractVerticle() { private fun JsonObject.filterSensitiveData(): JsonObject { this.remove(Props.PASSWORD) + this.remove(Props.SSH_KEY) return this } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt index 19cb04715..ddb05261d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt @@ -5,5 +5,6 @@ data class Credential( val label: String, val user: String, val password: String?, - val token: String? + val token: String?, + val sshKey: String? ) diff --git a/cogboard-webapp/src/components/CredentialForm/index.js b/cogboard-webapp/src/components/CredentialForm/index.js index 8cc72821a..9c80150b1 100644 --- a/cogboard-webapp/src/components/CredentialForm/index.js +++ b/cogboard-webapp/src/components/CredentialForm/index.js @@ -22,7 +22,8 @@ const CredentialsForm = ({ 'UsernameField', 'PasswordField', 'PasswordConfirmationField', - 'TokenField' + 'TokenField', + 'SSHKeyField' ]; const constraints = { @@ -82,7 +83,8 @@ CredentialsForm.defaultProps = { user: '', password: '', confirmationPassword: '', - token: '' + token: '', + sshKey: '' }; export default CredentialsForm; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index 33cad5104..f68c87a09 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -100,6 +100,21 @@ const dialogFields = { label: 'Token', validator: () => string() }, + SSHKeyField: { + component: MultilineTextInput, + name: 'sshKey', + label: 'SSH Private Key', + validator: () => + string() + .matches('^-----BEGIN OPENSSH PRIVATE KEY-----\n', { + message: vm.SSH_KEY_BEGIN, + excludeEmptyString: true + }) + .matches('\n-----END OPENSSH PRIVATE KEY-----$', { + message: vm.SSH_KEY_END, + excludeEmptyString: true + }) + }, PublicURL: { component: TextInput, name: 'publicUrl', diff --git a/cogboard-webapp/src/constants/index.js b/cogboard-webapp/src/constants/index.js index 3b83785d9..ca434023c 100644 --- a/cogboard-webapp/src/constants/index.js +++ b/cogboard-webapp/src/constants/index.js @@ -305,7 +305,10 @@ export const validationMessages = { INVALID_PUBLIC_URL: () => 'Invalid Public URL', FIELD_MIN_ITEMS: () => 'This field must have at least 1 item.', UNIQUE_FIELD: () => 'This field must be unique.', - PASSWORD_MATCH: () => 'Password must match.' + PASSWORD_MATCH: () => 'Password must match.', + SSH_KEY_BEGIN: () => + 'The key must begin with "-----BEGIN OPENSSH PRIVATE KEY-----"', + SSH_KEY_END: () => 'The key must end with "-----END OPENSSH PRIVATE KEY-----"' }; export const NOTIFICATIONS = { From 7d2f0b2b9ee5d085b7eee0a663c2ebf0c1e26b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 24 Oct 2021 17:42:05 +0200 Subject: [PATCH 033/226] Modify credential test --- .../cypress/fixtures/credentialsEndpoints.js | 3 ++- .../cypress/integration/credentials.js | 9 +++++++++ .../cypress-tests/cypress/support/credential.js | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js b/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js index 03e0430b5..b4f058601 100644 --- a/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js +++ b/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js @@ -4,7 +4,8 @@ export const badCredentials = () => { password: 'xxxxxxxxxxx', passwordConf: 'zzz', user: 'xxxxxxxxxxxxxxxxxxxxxxxxxx', - label: ' ' + label: ' ', + sshKey: 'xxx', }; }; diff --git a/functional/cypress-tests/cypress/integration/credentials.js b/functional/cypress-tests/cypress/integration/credentials.js index 572afbbf4..37a0b7874 100644 --- a/functional/cypress-tests/cypress/integration/credentials.js +++ b/functional/cypress-tests/cypress/integration/credentials.js @@ -25,6 +25,7 @@ describe('Credentials', () => { .applyPassword() .assertErrorMessageVisible('Password must match.', 'credential-form-auth') .applyToken() + .applySSHKey() .assertErrorMessageVisible( 'This field is required', 'credential-form-auth-label-input-error' @@ -32,6 +33,14 @@ describe('Credentials', () => { .assertErrorMessageVisible( 'Label length must be less or equal to 25.', 'credential-form-auth-user-input-error' + ) + .assertErrorMessageVisible( + 'The key must begin with "-----BEGIN OPENSSH PRIVATE KEY-----"', + 'credential-form-auth-ssh-key-input-error' + ) + .assertErrorMessageVisible( + 'The key must end with "-----END OPENSSH PRIVATE KEY-----"', + 'credential-form-auth-ssh-key-input-error' ); }); diff --git a/functional/cypress-tests/cypress/support/credential.js b/functional/cypress-tests/cypress/support/credential.js index 9ee77c41f..a757c8f96 100644 --- a/functional/cypress-tests/cypress/support/credential.js +++ b/functional/cypress-tests/cypress/support/credential.js @@ -58,6 +58,17 @@ class Credentials { return this; } + applySSHKey(config) { + if (config !== undefined) { + this.config = config; + } + cy.get('[data-cy="credential-form-auth-ssh-key-input"]') + .clear() + .type(this.config.sshKey) + .blur(); + return this; + } + save() { cy.get('[data-cy="credential-form-submit-button"]').click(); return this; @@ -77,7 +88,10 @@ class Credentials { } assertErrorMessageVisible(message, dataCYName) { - cy.contains(`[data-cy^="${dataCYName}"]`, message).should('is.visible'); + cy + .contains(`[data-cy^="${dataCYName}"]`, message) + .scrollIntoView() + .should('is.visible'); return this; } } From 5729dae71a4ec3830e8bea90b337293d12c88278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 24 Oct 2021 18:03:08 +0200 Subject: [PATCH 034/226] Change SSH Key regex --- cogboard-webapp/src/components/widgets/dialogFields/index.js | 4 ++-- cogboard-webapp/src/constants/index.js | 5 ++--- functional/cypress-tests/cypress/integration/credentials.js | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index f68c87a09..083518142 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -106,11 +106,11 @@ const dialogFields = { label: 'SSH Private Key', validator: () => string() - .matches('^-----BEGIN OPENSSH PRIVATE KEY-----\n', { + .matches('^-----BEGIN ([A-Z]{1,} )*PRIVATE KEY-----\n', { message: vm.SSH_KEY_BEGIN, excludeEmptyString: true }) - .matches('\n-----END OPENSSH PRIVATE KEY-----$', { + .matches('\n-----END ([A-Z]{1,} )*PRIVATE KEY-----$', { message: vm.SSH_KEY_END, excludeEmptyString: true }) diff --git a/cogboard-webapp/src/constants/index.js b/cogboard-webapp/src/constants/index.js index ca434023c..07da55449 100644 --- a/cogboard-webapp/src/constants/index.js +++ b/cogboard-webapp/src/constants/index.js @@ -306,9 +306,8 @@ export const validationMessages = { FIELD_MIN_ITEMS: () => 'This field must have at least 1 item.', UNIQUE_FIELD: () => 'This field must be unique.', PASSWORD_MATCH: () => 'Password must match.', - SSH_KEY_BEGIN: () => - 'The key must begin with "-----BEGIN OPENSSH PRIVATE KEY-----"', - SSH_KEY_END: () => 'The key must end with "-----END OPENSSH PRIVATE KEY-----"' + SSH_KEY_BEGIN: () => 'The key must begin with "-----BEGIN PRIVATE KEY-----"', + SSH_KEY_END: () => 'The key must end with "-----END PRIVATE KEY-----"' }; export const NOTIFICATIONS = { diff --git a/functional/cypress-tests/cypress/integration/credentials.js b/functional/cypress-tests/cypress/integration/credentials.js index 37a0b7874..878fe006f 100644 --- a/functional/cypress-tests/cypress/integration/credentials.js +++ b/functional/cypress-tests/cypress/integration/credentials.js @@ -35,11 +35,11 @@ describe('Credentials', () => { 'credential-form-auth-user-input-error' ) .assertErrorMessageVisible( - 'The key must begin with "-----BEGIN OPENSSH PRIVATE KEY-----"', + 'The key must begin with "-----BEGIN PRIVATE KEY-----"', 'credential-form-auth-ssh-key-input-error' ) .assertErrorMessageVisible( - 'The key must end with "-----END OPENSSH PRIVATE KEY-----"', + 'The key must end with "-----END PRIVATE KEY-----"', 'credential-form-auth-ssh-key-input-error' ); }); From b5a967ec3e17839c939414ee602d6794afa6ff26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 27 Oct 2021 14:57:12 +0200 Subject: [PATCH 035/226] Add OpenSSH docker image --- build.gradle.kts | 2 +- cogboard-local-compose.yml | 6 ++++++ settings.gradle.kts | 1 + ssh/Dockerfile | 9 +++++++++ ssh/build.gradle.kts | 14 ++++++++++++++ ssh/gen.sh | 20 ++++++++++++++++++++ 6 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 ssh/Dockerfile create mode 100644 ssh/build.gradle.kts create mode 100755 ssh/gen.sh diff --git a/build.gradle.kts b/build.gradle.kts index 4451a9a0c..61c3cb243 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,7 +55,7 @@ allprojects { tasks { named("build") { - dependsOn(":cogboard-app:test", ":cogboard-webapp:buildImage") + dependsOn(":cogboard-app:test", ":cogboard-webapp:buildImage", ":ssh:buildImage") } register("cypressInit", Exec::class) { setWorkingDir("./functional/cypress-tests") diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 327aeb6f9..f9b6fa54a 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -16,6 +16,12 @@ services: - cognet command: ["--no-request-journal", "--global-response-templating"] + ssh-server: + image: ssh-server + hostname: ssh-server + networks: + - cognet + backend: image: "cogboard/cogboard-app:${COGBOARD_VERSION}" environment: diff --git a/settings.gradle.kts b/settings.gradle.kts index 0700e243a..dd77ec502 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,3 +17,4 @@ rootProject.name = "cogboard" include("cogboard-app") include("cogboard-webapp") +include("ssh") \ No newline at end of file diff --git a/ssh/Dockerfile b/ssh/Dockerfile new file mode 100644 index 000000000..34debc3ee --- /dev/null +++ b/ssh/Dockerfile @@ -0,0 +1,9 @@ +FROM lscr.io/linuxserver/openssh-server + +ENV PUBLIC_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICLzgU4PuqFGnQCd5A5Gl30ssE3b9fW7gcyvES4xHHUo mock@openssh-server" +ENV PASSWORD_ACCESS=true +ENV USER_NAME=mock +ENV USER_PASSWORD=TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E + +ADD gen.sh /home/mock/gen.sh +RUN /home/mock/gen.sh 50 > /home/mock/example.txt \ No newline at end of file diff --git a/ssh/build.gradle.kts b/ssh/build.gradle.kts new file mode 100644 index 000000000..13232d3c4 --- /dev/null +++ b/ssh/build.gradle.kts @@ -0,0 +1,14 @@ +import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage + +plugins { + id("base") + id("com.bmuschko.docker-remote-api") +} + +tasks { + register("buildImage") { + group = "docker" + inputDir.set(file(projectDir)) + images.add("ssh-server") + } +} \ No newline at end of file diff --git a/ssh/gen.sh b/ssh/gen.sh new file mode 100755 index 000000000..b4b511bf7 --- /dev/null +++ b/ssh/gen.sh @@ -0,0 +1,20 @@ +#!/bin/bash +labels=(DEBUG INFO WARNING ERROR) +words=(Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sit amet massa sit amet mi feugiat lobortis. Morbi ultrices hendrerit luctus. Donec fermentum viverra viverra. Integer convallis sapien sit amet facilisis pretium. Ut quis commodo odio, ac bibendum urna. Ut imperdiet ante sed ex sollicitudin auctor. Nulla vel eros sit amet velit vulputate suscipit a malesuada felis. Curabitur commodo, erat eget condimentum tristique, ante diam porttitor nulla, condimentum vulputate nibh ex sit amet velit. Vestibulum vel lectus bibendum, pellentesque nisl id, vehicula tellus. Suspendisse sem turpis, dignissim quis aliquet nec, laoreet sit amet urna. Nulla non euismod tellus, id varius) + +COUNT=5 +if ! [ -z "$1" ]; then + COUNT=$1 +fi + +for run in $( seq 1 $COUNT ); do + d=$(date +%Y-%m-%d:%H:%M:%S) + l=${labels[$(($RANDOM%4))]} + w=() + wCount=$(($RANDOM%5+5)) + for (( i=0; i<$wCount; i++ )) + do + w[i]=${words[$(($RANDOM%${#words[@]}))]} + done + echo $d "*"${l}"* [FelixStartLevel] " ${w[*]} +done \ No newline at end of file From 8acac9610c6b2f3d43bfd0f58f1f3c5dbebc8e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Wed, 27 Oct 2021 22:12:19 +0200 Subject: [PATCH 036/226] Deleted SSH tests to move them to different branch --- api-mocks/__files/logViewer/logs.json | 10 +++- api-mocks/mappings/endpoints-mapping.json | 2 +- .../cognifide/cogboard/CogboardConstants.kt | 2 + .../com/cognifide/cogboard/ssh/SSHClient.kt | 7 ++- .../cogboard/ssh/auth/SSHAuthData.kt | 1 + .../strategy/BasicAuthSessionStrategy.kt | 2 +- .../strategy/SSHKeyAuthSessionStrategy.kt | 2 +- .../cognifide/cogboard/widget/SSHWidget.kt | 52 ----------------- .../cognifide/cogboard/widget/WidgetIndex.kt | 15 ++++- .../connectionStrategy/ConnectionStrategy.kt | 14 +++++ .../ConnectionStrategyFactory.kt | 29 +++++----- .../HTTPConnectionStrategy.kt | 10 ---- .../HttpConnectionStrategy.kt | 51 ++++++++++++++++ .../SSHConnectionStrategy.kt | 29 +++++++++- .../cogboard/widget/type/LogViewerWidget.kt | 49 +++++++++++----- .../cogboard/widget/type/LogViewerTest.kt | 58 +++++++++++++++++++ 16 files changed, 232 insertions(+), 101 deletions(-) delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt create mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt diff --git a/api-mocks/__files/logViewer/logs.json b/api-mocks/__files/logViewer/logs.json index b4211a0e3..e5c4e7400 100644 --- a/api-mocks/__files/logViewer/logs.json +++ b/api-mocks/__files/logViewer/logs.json @@ -1,3 +1,11 @@ { - "logLines": "02:49:12 127.0.0.1 GET / 200\n02:49:35 127.0.0.1 GET /index.html 200\n03:01:06 127.0.0.1 GET /images/sponsered.gif 304\n03:52:36 127.0.0.1 GET /search.php 200\n04:17:03 127.0.0.1 GET /admin/style.css 200\n05:04:54 127.0.0.1 GET /favicon.ico 404\n05:38:07 127.0.0.1 GET /js/ads.js 200\n" + "logLines": [ + "02:49:12 127.0.0.1 GET / 200", + "02:49:35 127.0.0.1 GET /index.html 200", + "03:01:06 127.0.0.1 GET /images/sponsered.gif 304", + "03:52:36 127.0.0.1 GET /search.php 200", + "04:17:03 127.0.0.1 GET /admin/style.css 200", + "05:04:54 127.0.0.1 GET /favicon.ico 404", + "05:38:07 127.0.0.1 GET /js/ads.js 200" + ] } \ No newline at end of file diff --git a/api-mocks/mappings/endpoints-mapping.json b/api-mocks/mappings/endpoints-mapping.json index 8dc85fa63..d98b991d2 100644 --- a/api-mocks/mappings/endpoints-mapping.json +++ b/api-mocks/mappings/endpoints-mapping.json @@ -3,7 +3,7 @@ { "request": { "method": "GET", - "url": "/logviewer", + "url": "/log-viewer", "queryParameters": { "lines": { "matches": "^[0-9]+$" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index 25c07c991..c7ed23833 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -40,12 +40,14 @@ class CogboardConstants { const val SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds const val SSH_TIMEOUT = 5000 // 5000ms -> 5s const val SSH_HOST = "sshAddress" + const val SSH_PORT = "sshPort" const val SSH_KEY = "sshKey" const val SSH_KEY_PASSPHRASE = "sshKeyPassphrase" const val URL = "url" const val LOG_SOURCE = "logSource" const val LOG_SOURCE_TYPE = "logSourceType" + const val LOG_REQUEST_TYPE = "logRequestType" const val LOG_LINES = "logLines" const val LOG_FILE_PATH = "logFilePath" const val REQUEST_ID = "requestId" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index f25b0e32d..46e8467a3 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -41,7 +41,7 @@ class SSHClient : AbstractVerticle() { } } - private fun tryToConnect(config: JsonObject) { + fun tryToConnect(config: JsonObject) { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) try { connect(config) @@ -68,8 +68,9 @@ class SSHClient : AbstractVerticle() { private fun initSSHSession(authData: SSHAuthData) { jsch = JSch() - jsch.setKnownHosts("~/.ssh/known_hosts") - val session = SessionStrategyFactory(jsch).create(authData).initSession() + // jsch.setKnownHosts("~/.ssh/known_hosts") for security reasons this should be used + session = SessionStrategyFactory(jsch).create(authData).initSession() + session.setConfig("StrictHostKeyChecking", "no") // not secure session.connect(CogboardConstants.Props.SSH_TIMEOUT) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index b2f6690f6..446b679fd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -14,6 +14,7 @@ class SSHAuthData(private val config: JsonObject) { val token = config.getString(CogboardConstants.Props.TOKEN) ?: "" val key = config.getString(CogboardConstants.Props.SSH_KEY) ?: "" val host = config.getString(CogboardConstants.Props.SSH_HOST) ?: "" + val port = config.getInteger(CogboardConstants.Props.SSH_PORT) ?: 22 val authenticationType = fromConfigAuthenticationType() private fun fromConfigAuthenticationType(): AuthenticationType { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt index 96920b28e..119d0293a 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/BasicAuthSessionStrategy.kt @@ -7,7 +7,7 @@ import com.jcraft.jsch.Session class BasicAuthSessionStrategy(jsch: JSch, authData: SSHAuthData) : SessionStrategy(jsch, authData) { override fun initSession(): Session { - val session = jsch.getSession(authData.user, authData.host) + val session = jsch.getSession(authData.user, authData.host, authData.port) session.setPassword(securityString) return session diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt index 65e7c5b72..07978dae7 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/strategy/SSHKeyAuthSessionStrategy.kt @@ -11,7 +11,7 @@ class SSHKeyAuthSessionStrategy(jSch: JSch, authData: SSHAuthData) : SessionStra } else { jsch.addIdentity(securityString, authData.password) } - val session = jsch.getSession(authData.user, authData.host) + val session = jsch.getSession(authData.user, authData.host, authData.port) session.setConfig("PreferredAuthentications", "publickey") return session diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt deleted file mode 100644 index 43939a695..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/SSHWidget.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.cognifide.cogboard.widget - -import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.CogboardConstants.Event -import com.cognifide.cogboard.config.service.BoardsConfigService -import io.vertx.core.Vertx -import io.vertx.core.eventbus.MessageConsumer -import io.vertx.core.json.JsonObject -import java.nio.Buffer - -abstract class SSHWidget( - vertx: Vertx, - config: JsonObject, - serv: BoardsConfigService -) : AsyncWidget(vertx, config, serv) { - val sshKey: String = config.endpointProp(Props.SSH_KEY) - val host: String = config.endpointProp(Props.SSH_HOST) - val logPath: String = config.endpointProp(Props.LOG_FILE_PATH) - val logLines: String = config.endpointProp(Props.LOG_LINES) - private lateinit var sshConsumer: MessageConsumer - - fun registerForSSH(eventBusAddress: String) { - sshConsumer = vertx.eventBus() - .consumer(eventBusAddress) - .handler { - handleSSHResponse(it.body()) - } - } - - abstract fun handleSSHResponse(body: Buffer?) - - fun unregisterFromSSH() { - if (::sshConsumer.isInitialized) { - sshConsumer.unregister() - } - } - - fun sendRequestForLogs(config: JsonObject) { - ensureConfigIsPrepared(config) - vertx.eventBus().send(Event.SSH_COMMAND, config) - } - - private fun ensureConfigIsPrepared(config: JsonObject) { - config.getString(Props.USER) ?: config.put(Props.USER, user) - config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, password) - config.getString(Props.TOKEN) ?: config.put(Props.TOKEN, token) - config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, sshKey) - config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, host) - config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, logPath) - config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, logLines) - } -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt index a73e9feb6..e057a29be 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt @@ -3,7 +3,20 @@ package com.cognifide.cogboard.widget import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.storage.VolumeStorageFactory.appConfig -import com.cognifide.cogboard.widget.type.* +import com.cognifide.cogboard.widget.type.AemBundleInfoWidget +import com.cognifide.cogboard.widget.type.AemHealthcheckWidget +import com.cognifide.cogboard.widget.type.BambooDeploymentWidget +import com.cognifide.cogboard.widget.type.BambooPlanWidget +import com.cognifide.cogboard.widget.type.CheckboxWidget +import com.cognifide.cogboard.widget.type.IframeEmbedWidget +import com.cognifide.cogboard.widget.type.JenkinsJobWidget +import com.cognifide.cogboard.widget.type.JiraBucketsWidget +import com.cognifide.cogboard.widget.type.LinkListWidget +import com.cognifide.cogboard.widget.type.WhiteSpaceWidget +import com.cognifide.cogboard.widget.type.ServiceCheckWidget +import com.cognifide.cogboard.widget.type.TextWidget +import com.cognifide.cogboard.widget.type.ToDoListWidget +import com.cognifide.cogboard.widget.type.WorldClockWidget import com.cognifide.cogboard.widget.type.randompicker.RandomPickerWidget import com.cognifide.cogboard.widget.type.sonarqube.SonarQubeWidget import com.cognifide.cogboard.widget.type.zabbix.ZabbixWidget diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index df6726528..74f1025ea 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -1,9 +1,23 @@ package com.cognifide.cogboard.widget.connectionStrategy +import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.http.auth.AuthenticationType import io.vertx.core.Vertx +import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject abstract class ConnectionStrategy(protected val vertx: Vertx) { + protected fun JsonObject.endpointProp(prop: String): String { + return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" + } + + protected open fun authenticationTypes(): Set { + return setOf(AuthenticationType.BASIC) + } abstract fun connectAndGetResources(address: String, arguments: JsonObject) + + abstract fun getConsumer(eventBusAddress: String): MessageConsumer<*> + + abstract fun handleResponse(response: Any): String } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt index 9fd11187d..d728ff155 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -1,30 +1,31 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants.ConnectionType +import com.cognifide.cogboard.CogboardConstants.Props import io.vertx.core.Vertx -import java.lang.Exception +import io.vertx.core.json.JsonObject -class ConnectionStrategyFactory { - class ConnectionTypeException : Exception("Unknown strategy type") - class MissingVertxException : Exception("Vertx hasn't been added to factory instance") +class UnknownConnectionTypeException( + message: String? +) : RuntimeException(message) - private lateinit var vertx: Vertx +class ConnectionStrategyFactory( + private var vertx: Vertx, + props: JsonObject +) { + + private val connectionType = props.getString(Props.LOG_SOURCE_TYPE) fun addVertxInstance(vertx: Vertx): ConnectionStrategyFactory { this.vertx = vertx - return this } - fun build(type: String): ConnectionStrategy { - if (!::vertx.isInitialized) { - throw MissingVertxException() - } - - return when (type) { - ConnectionType.HTTP -> HTTPConnectionStrategy(vertx) + fun build(): ConnectionStrategy { + return when (connectionType) { + ConnectionType.HTTP -> HttpConnectionStrategy(vertx) ConnectionType.SSH -> SSHConnectionStrategy(vertx) - else -> throw ConnectionTypeException() + else -> throw UnknownConnectionTypeException("Unknown strategy type") } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt deleted file mode 100644 index 3d9423057..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HTTPConnectionStrategy.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.cognifide.cogboard.widget.connectionStrategy - -import io.vertx.core.Vertx -import io.vertx.core.json.JsonObject - -class HTTPConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - TODO("Not yet implemented") - } -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt new file mode 100644 index 000000000..a5728fe49 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt @@ -0,0 +1,51 @@ +package com.cognifide.cogboard.widget.connectionStrategy + +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.Event +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.GET +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.PUT +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.POST +import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.DELETE +import io.vertx.core.Vertx +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.Json +import io.vertx.core.json.JsonObject + +class HttpConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { + override fun connectAndGetResources(address: String, arguments: JsonObject) { + when (arguments.getString(Props.LOG_REQUEST_TYPE, "")) { + GET -> vertx.eventBus().send(Event.HTTP_GET, getProps(arguments)) + PUT -> vertx.eventBus().send(Event.HTTP_PUT, putProps(arguments)) + POST -> vertx.eventBus().send(Event.HTTP_POST, postProps(arguments)) + DELETE -> vertx.eventBus().send(Event.HTTP_DELETE, basicProps(arguments)) + } + } + + override fun getConsumer(eventBusAddress: String): MessageConsumer<*> = + vertx.eventBus().consumer(eventBusAddress) + + override fun handleResponse(response: Any): String = + (response as JsonObject).getString(Props.LOG_LINES) + + private fun basicProps(props: JsonObject): JsonObject = + JsonObject() + .put(Props.URL, props.endpointProp(Props.URL)) + .put(Props.EVENT_ADDRESS, props.endpointProp(Props.EVENT_ADDRESS)) + .put(Props.USER, props.endpointProp(Props.USER)) + .put(Props.PASSWORD, props.endpointProp(Props.PASSWORD)) + .put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + .put(Props.CONTENT_TYPE, props.endpointProp(Props.CONTENT_TYPE)) + + private fun getProps(props: JsonObject): JsonObject = + basicProps(props) + .put(Props.REQUEST_ID, props.getValue(Props.REQUEST_ID, "")) + .put(Props.TOKEN, props.endpointProp(Props.TOKEN)) + + private fun putProps(props: JsonObject): JsonObject = + basicProps(props) + .put(Props.BODY, props.getJsonObject(Props.BODY)) + + private fun postProps(props: JsonObject): JsonObject = + basicProps(props) + .put(Props.BODY, props.getJsonObject(Props.BODY)) +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index 2d250c359..13f59c16b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -1,10 +1,35 @@ package com.cognifide.cogboard.widget.connectionStrategy +import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.CogboardConstants.Props import io.vertx.core.Vertx +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.Json import io.vertx.core.json.JsonObject +import java.nio.charset.Charset -class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { +open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { override fun connectAndGetResources(address: String, arguments: JsonObject) { - TODO("Not yet implemented") + val config = ensureConfigIsPrepared(arguments) + vertx.eventBus().send(CogboardConstants.Event.SSH_COMMAND, config) + } + + override fun getConsumer(eventBusAddress: String): MessageConsumer<*> = + vertx.eventBus().consumer(eventBusAddress) + + override fun handleResponse(response: Any): String = + (response as Buffer).toString(Charset.defaultCharset()) + + private fun ensureConfigIsPrepared(config: JsonObject): JsonObject { + config.getString(Props.USER) ?: config.put(Props.USER, config.endpointProp(Props.USER)) + config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, config.endpointProp(Props.PASSWORD)) + config.getString(Props.TOKEN) ?: config.put(Props.TOKEN, config.endpointProp(Props.TOKEN)) + config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, config.endpointProp(Props.SSH_KEY)) + config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, config.endpointProp(Props.SSH_HOST)) + config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, config.endpointProp(Props.LOG_FILE_PATH)) + config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, config.endpointProp(Props.LOG_LINES)) + config.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + return config } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 67117b02b..9b08fd40d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -7,8 +7,11 @@ import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import io.vertx.core.Vertx +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject +import java.nio.charset.Charset class LogViewerWidget( vertx: Vertx, @@ -16,20 +19,25 @@ class LogViewerWidget( serv: BoardsConfigService ) : BaseWidget(vertx, config, serv) { private val address = config.getString(Props.LOG_SOURCE) - private val lines = config.getInteger(Props.LOG_LINES) + private val lines = config.getString(Props.LOG_LINES) + private val connectionType = config.getString(Props.LOG_SOURCE_TYPE) - private lateinit var consumer: MessageConsumer - private var connectionStrategy: ConnectionStrategy = determineConnectionStrategy(config) + private var consumer: MessageConsumer<*>? = null + private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() override fun start(): Widget { - consumer = vertx.eventBus() - .consumer(eventBusAddress) - .handler { - handleResponse(it.body()) - } + consumer = connectionStrategy.getConsumer(eventBusAddress) + consumer!!.handler { + handleResponse(it) + } return super.start() } + override fun stop(): Widget { + consumer?.unregister() + return super.stop() + } + override fun updateState() { if (address.isNotBlank()) { connectionStrategy.connectAndGetResources(address, config) @@ -38,24 +46,35 @@ class LogViewerWidget( } } + private fun handleResponse(response: Message<*>) { + val responseBody = response.body() + if (responseBody is JsonObject) { + if (checkAuthorized(responseBody)) { + send(prepareLogs(connectionStrategy.handleResponse(responseBody))) + } + } else { + send(prepareLogs(connectionStrategy.handleResponse(responseBody))) + } + } private fun handleResponse(responseBody: JsonObject) { if (checkAuthorized(responseBody)) { val logs = responseBody.getString(Props.LOG_LINES) - send(prepareLogs(logs)) } } + private fun handleResponse(responseBody: Buffer) { + val logs = prepareLogs(responseBody.toString(Charset.defaultCharset())) + send(logs) + } + private fun prepareLogs(logs: String): String { // TODO return logs } - private fun determineConnectionStrategy(config: JsonObject): ConnectionStrategy { - val type = config.getString(Props.LOG_SOURCE_TYPE) - - return ConnectionStrategyFactory() + private fun determineConnectionStrategy() = + ConnectionStrategyFactory(vertx, config) .addVertxInstance(vertx) - .build(type) - } + .build() } diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt new file mode 100644 index 000000000..896b60b27 --- /dev/null +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt @@ -0,0 +1,58 @@ +package com.cognifide.cogboard.widget.type + +import com.cognifide.cogboard.CogboardConstants.ConnectionType +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.CogboardConstants.RequestMethod +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.JsonObject +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* + +class LogViewerTest: WidgetTestBase() { + + private lateinit var widget: LogViewerWidget + + override fun widgetName(): String { + return "LogViewerWidget" + } + + @BeforeEach + fun initForTest() { + super.init() + } + + @Test + fun `Expect Buffer consumer to be used when type is SSH`() { + val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer + `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + + val config = initWidget() + .put(Props.LOG_SOURCE_TYPE, ConnectionType.SSH) + .put(Props.LOG_SOURCE, "192.168.0.1") + .put(Props.LOG_LINES, "5") + widget = LogViewerWidget(vertx, config, initService()) + + widget.start() + + verify(eventBus).consumer(eq(widget.eventBusAddress)) + } + + @Test + fun `Expect JsonObject consumer to be used when type is HTTP`() { + val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer + `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + + val config = initWidget() + .put(Props.LOG_SOURCE_TYPE, ConnectionType.HTTP) + .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) + .put(Props.LOG_SOURCE, "192.168.0.1") + .put(Props.LOG_LINES, "5") + widget = LogViewerWidget(vertx, config, initService()) + + widget.start() + + verify(eventBus).consumer(eq(widget.eventBusAddress)) + } +} \ No newline at end of file From 9b2ffcf69b15df907d1fd1e8902ac9fd87e8a478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Wed, 27 Oct 2021 23:26:06 +0200 Subject: [PATCH 037/226] Code review changes [#381] --- .../cogboard/ssh/auth/AuthenticationType.kt | 1 - .../cogboard/ssh/auth/SSHAuthData.kt | 3 --- .../ssh/session/SessionStrategyFactory.kt | 3 +-- .../connectionStrategy/ConnectionStrategy.kt | 2 +- .../ConnectionStrategyFactory.kt | 15 ++++++----- .../HttpConnectionStrategy.kt | 2 +- .../SSHConnectionStrategy.kt | 27 +++++++++++-------- .../cogboard/widget/type/LogViewerWidget.kt | 19 ++++--------- 8 files changed, 32 insertions(+), 40 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt index abca29a42..1dfa64105 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/AuthenticationType.kt @@ -2,6 +2,5 @@ package com.cognifide.cogboard.ssh.auth enum class AuthenticationType { BASIC, - TOKEN, SSH_KEY } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index 446b679fd..ee0d41ea8 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -2,7 +2,6 @@ package com.cognifide.cogboard.ssh.auth import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.ssh.auth.AuthenticationType.BASIC -import com.cognifide.cogboard.ssh.auth.AuthenticationType.TOKEN import com.cognifide.cogboard.ssh.auth.AuthenticationType.SSH_KEY import io.vertx.core.json.Json import io.vertx.core.json.JsonArray @@ -29,7 +28,6 @@ class SSHAuthData(private val config: JsonObject) { private fun hasAuthTypeCorrectCredentials(authType: AuthenticationType): Boolean = when { - authType == TOKEN && user.isNotBlank() && token.isNotBlank() -> true authType == SSH_KEY && key.isNotBlank() -> true else -> authType == BASIC && user.isNotBlank() && password.isNotBlank() } @@ -37,7 +35,6 @@ class SSHAuthData(private val config: JsonObject) { fun getAuthenticationString(): String = when (authenticationType) { BASIC -> config.getString(CogboardConstants.Props.PASSWORD) - TOKEN -> config.getString(CogboardConstants.Props.TOKEN) SSH_KEY -> config.getString(CogboardConstants.Props.SSH_KEY) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt index 88b54dd2c..cc7df076e 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/session/SessionStrategyFactory.kt @@ -1,7 +1,6 @@ package com.cognifide.cogboard.ssh.session import com.cognifide.cogboard.ssh.auth.AuthenticationType.BASIC -import com.cognifide.cogboard.ssh.auth.AuthenticationType.TOKEN import com.cognifide.cogboard.ssh.auth.AuthenticationType.SSH_KEY import com.cognifide.cogboard.ssh.auth.SSHAuthData import com.cognifide.cogboard.ssh.session.strategy.BasicAuthSessionStrategy @@ -12,7 +11,7 @@ import com.jcraft.jsch.JSch class SessionStrategyFactory(private val jsch: JSch) { fun create(authData: SSHAuthData): SessionStrategy = when (authData.authenticationType) { - BASIC, TOKEN -> { + BASIC -> { BasicAuthSessionStrategy(jsch, authData) } SSH_KEY -> { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index 74f1025ea..fcf425be2 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -15,7 +15,7 @@ abstract class ConnectionStrategy(protected val vertx: Vertx) { return setOf(AuthenticationType.BASIC) } - abstract fun connectAndGetResources(address: String, arguments: JsonObject) + abstract fun sendRequest(address: String, arguments: JsonObject) abstract fun getConsumer(eventBusAddress: String): MessageConsumer<*> diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt index d728ff155..2e91d756f 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -1,14 +1,11 @@ package com.cognifide.cogboard.widget.connectionStrategy -import com.cognifide.cogboard.CogboardConstants.ConnectionType +import com.cognifide.cogboard.CogboardConstants.ConnectionType.Companion.HTTP +import com.cognifide.cogboard.CogboardConstants.ConnectionType.Companion.SSH import com.cognifide.cogboard.CogboardConstants.Props import io.vertx.core.Vertx import io.vertx.core.json.JsonObject -class UnknownConnectionTypeException( - message: String? -) : RuntimeException(message) - class ConnectionStrategyFactory( private var vertx: Vertx, props: JsonObject @@ -23,9 +20,13 @@ class ConnectionStrategyFactory( fun build(): ConnectionStrategy { return when (connectionType) { - ConnectionType.HTTP -> HttpConnectionStrategy(vertx) - ConnectionType.SSH -> SSHConnectionStrategy(vertx) + HTTP -> HttpConnectionStrategy(vertx) + SSH -> SSHConnectionStrategy(vertx) else -> throw UnknownConnectionTypeException("Unknown strategy type") } } } + +class UnknownConnectionTypeException( + message: String? +) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt index a5728fe49..f7cff2891 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt @@ -12,7 +12,7 @@ import io.vertx.core.json.Json import io.vertx.core.json.JsonObject class HttpConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { + override fun sendRequest(address: String, arguments: JsonObject) { when (arguments.getString(Props.LOG_REQUEST_TYPE, "")) { GET -> vertx.eventBus().send(Event.HTTP_GET, getProps(arguments)) PUT -> vertx.eventBus().send(Event.HTTP_PUT, putProps(arguments)) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index 13f59c16b..c88316796 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -10,8 +10,8 @@ import io.vertx.core.json.JsonObject import java.nio.charset.Charset open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { - override fun connectAndGetResources(address: String, arguments: JsonObject) { - val config = ensureConfigIsPrepared(arguments) + override fun sendRequest(address: String, arguments: JsonObject) { + val config = prepareConfig(arguments) vertx.eventBus().send(CogboardConstants.Event.SSH_COMMAND, config) } @@ -21,15 +21,20 @@ open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { override fun handleResponse(response: Any): String = (response as Buffer).toString(Charset.defaultCharset()) - private fun ensureConfigIsPrepared(config: JsonObject): JsonObject { - config.getString(Props.USER) ?: config.put(Props.USER, config.endpointProp(Props.USER)) - config.getString(Props.PASSWORD) ?: config.put(Props.PASSWORD, config.endpointProp(Props.PASSWORD)) - config.getString(Props.TOKEN) ?: config.put(Props.TOKEN, config.endpointProp(Props.TOKEN)) - config.getString(Props.SSH_KEY) ?: config.put(Props.SSH_KEY, config.endpointProp(Props.SSH_KEY)) - config.getString(Props.SSH_HOST) ?: config.put(Props.SSH_HOST, config.endpointProp(Props.SSH_HOST)) - config.getString(Props.LOG_FILE_PATH) ?: config.put(Props.LOG_FILE_PATH, config.endpointProp(Props.LOG_FILE_PATH)) - config.getString(Props.LOG_LINES) ?: config.put(Props.LOG_LINES, config.endpointProp(Props.LOG_LINES)) - config.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + private fun prepareConfig(config: JsonObject): JsonObject { + val tmpConfig = prepareConfigLines(config = config, + Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_HOST, Props.SSH_KEY, Props.SSH_PORT, + Props.LOG_FILE_PATH, Props.LOG_LINES, Props.SSH_KEY_PASSPHRASE + ) + + tmpConfig.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + return tmpConfig + } + + private fun prepareConfigLines(config: JsonObject, vararg fields: String): JsonObject { + for (field in fields) { + config.getString(field) ?: config.put(field, config.endpointProp(field)) + } return config } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 9b08fd40d..438e3e290 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -7,11 +7,9 @@ import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import io.vertx.core.Vertx -import io.vertx.core.buffer.Buffer import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject -import java.nio.charset.Charset class LogViewerWidget( vertx: Vertx, @@ -40,7 +38,7 @@ class LogViewerWidget( override fun updateState() { if (address.isNotBlank()) { - connectionStrategy.connectAndGetResources(address, config) + connectionStrategy.sendRequest(address, config) } else { sendConfigurationError("Endpoint URL is blank") } @@ -49,25 +47,18 @@ class LogViewerWidget( private fun handleResponse(response: Message<*>) { val responseBody = response.body() if (responseBody is JsonObject) { - if (checkAuthorized(responseBody)) { - send(prepareLogs(connectionStrategy.handleResponse(responseBody))) - } + handleHttpResponse(responseBody) } else { send(prepareLogs(connectionStrategy.handleResponse(responseBody))) } } - private fun handleResponse(responseBody: JsonObject) { + + private fun handleHttpResponse(responseBody: JsonObject) { if (checkAuthorized(responseBody)) { - val logs = responseBody.getString(Props.LOG_LINES) - send(prepareLogs(logs)) + send(prepareLogs(connectionStrategy.handleResponse(responseBody))) } } - private fun handleResponse(responseBody: Buffer) { - val logs = prepareLogs(responseBody.toString(Charset.defaultCharset())) - send(logs) - } - private fun prepareLogs(logs: String): String { // TODO return logs From 82945f29f7f05407ad09e3169387124bed1a39ca Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 2 Nov 2021 20:04:38 +0100 Subject: [PATCH 038/226] Add ssh to credentials tests, Turn off LogViewer backend --- .../cogboard/config/EndpointLoader.kt | 1 + .../cogboard/widget/type/LogViewerWidget.kt | 87 ++++++------- .../main/resources/initData/credentials.json | 3 +- .../cognifide/cogboard/config/EndpointTest.kt | 10 +- .../cogboard/widget/type/LogViewerTest.kt | 116 +++++++++--------- .../cogboard/config/credentials-test.json | 6 +- 6 files changed, 118 insertions(+), 105 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt index 97609a8c2..0165b1c71 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt @@ -31,6 +31,7 @@ class EndpointLoader( this.put(Props.USER, credentials.getString(Props.USER) ?: "") this.put(Props.PASSWORD, credentials.getString(Props.PASSWORD) ?: "") this.put(Props.TOKEN, credentials.getString(Props.TOKEN) ?: "") + this.put(Props.SSH_KEY, credentials.getString(Props.SSH_KEY) ?: "") } } return this diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 438e3e290..82c7d1cf2 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -3,11 +3,7 @@ package com.cognifide.cogboard.widget.type import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.widget.BaseWidget -import com.cognifide.cogboard.widget.Widget -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import io.vertx.core.Vertx -import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject @@ -20,52 +16,59 @@ class LogViewerWidget( private val lines = config.getString(Props.LOG_LINES) private val connectionType = config.getString(Props.LOG_SOURCE_TYPE) + private val user: String = config.endpointProp(Props.USER) + private val password: String = config.endpointProp(Props.PASSWORD) + private val token: String = config.endpointProp(Props.TOKEN) + private val sshKey: String = config.endpointProp(Props.SSH_KEY) + private val url: String = config.endpointProp(Props.URL) + private val publicUrl: String = config.endpointProp(Props.PUBLIC_URL).ifBlank { url } + private var consumer: MessageConsumer<*>? = null - private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() + // private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() - override fun start(): Widget { - consumer = connectionStrategy.getConsumer(eventBusAddress) - consumer!!.handler { - handleResponse(it) - } - return super.start() - } + // override fun start(): Widget { + // consumer = connectionStrategy.getConsumer(eventBusAddress) + // consumer!!.handler { + // handleResponse(it) + // } + // return super.start() + // } - override fun stop(): Widget { - consumer?.unregister() - return super.stop() - } + // override fun stop(): Widget { + // consumer?.unregister() + // return super.stop() + // } override fun updateState() { - if (address.isNotBlank()) { - connectionStrategy.sendRequest(address, config) - } else { - sendConfigurationError("Endpoint URL is blank") - } + // if (address.isNotBlank()) { + // connectionStrategy.sendRequest(address, config) + // } else { + // sendConfigurationError("Endpoint URL is blank") + // } } - private fun handleResponse(response: Message<*>) { - val responseBody = response.body() - if (responseBody is JsonObject) { - handleHttpResponse(responseBody) - } else { - send(prepareLogs(connectionStrategy.handleResponse(responseBody))) - } - } + // private fun handleResponse(response: Message<*>) { + // val responseBody = response.body() + // if (responseBody is JsonObject) { + // handleHttpResponse(responseBody) + // } else { + // send(prepareLogs(connectionStrategy.handleResponse(responseBody))) + // } + // } - private fun handleHttpResponse(responseBody: JsonObject) { - if (checkAuthorized(responseBody)) { - send(prepareLogs(connectionStrategy.handleResponse(responseBody))) - } - } + // private fun handleHttpResponse(responseBody: JsonObject) { + // if (checkAuthorized(responseBody)) { + // send(prepareLogs(connectionStrategy.handleResponse(responseBody))) + // } + // } - private fun prepareLogs(logs: String): String { - // TODO - return logs - } + // private fun prepareLogs(logs: String): String { + // // TODO + // return logs + // } - private fun determineConnectionStrategy() = - ConnectionStrategyFactory(vertx, config) - .addVertxInstance(vertx) - .build() + // private fun determineConnectionStrategy() = + // ConnectionStrategyFactory(vertx, config) + // .addVertxInstance(vertx) + // .build() } diff --git a/cogboard-app/src/main/resources/initData/credentials.json b/cogboard-app/src/main/resources/initData/credentials.json index db175e834..b3e4b8e77 100644 --- a/cogboard-app/src/main/resources/initData/credentials.json +++ b/cogboard-app/src/main/resources/initData/credentials.json @@ -2,10 +2,11 @@ "credentials": [ { "token": "", + "sshKey": "", "password": "admin", "user": "admin", "label": "Zabbix", "id": "credential1" } ] -} \ No newline at end of file +} diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt index 2705faf5d..fbfa08da7 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt @@ -37,10 +37,12 @@ internal class EndpointTest { assert(validEndpoint.containsKey("user")) assert(validEndpoint.containsKey("password")) assert(validEndpoint.containsKey("token")) + assert(validEndpoint.containsKey("sshKey")) assert(invalidEndpoint.containsKey("user")) assert(invalidEndpoint.containsKey("password")) assert(invalidEndpoint.containsKey("token")) + assert(invalidEndpoint.containsKey("sshKey")) } @Test @@ -48,6 +50,7 @@ internal class EndpointTest { assertEquals("user1", validEndpoint.getString("user")) assertEquals("password1", validEndpoint.getString("password")) assertEquals("token1", validEndpoint.getString("token")) + assertEquals("key1", validEndpoint.getString("sshKey")) } @Test @@ -55,6 +58,7 @@ internal class EndpointTest { assertEquals("", invalidEndpoint.getString("user")) assertEquals("", invalidEndpoint.getString("password")) assertEquals("", invalidEndpoint.getString("token")) + assertEquals("", invalidEndpoint.getString("sshKey")) } @Test @@ -67,7 +71,8 @@ internal class EndpointTest { "publicUrl" : "Public Url", "user" : "user1", "password" : "password1", - "token" : "token1" + "token" : "token1", + "sshKey": "key1" } """) @@ -83,7 +88,8 @@ internal class EndpointTest { "url" : "url", "user" : "", "password" : "", - "token" : "" + "token" : "", + "sshKey" : "" } """) diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt index 896b60b27..afbee65b1 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt @@ -1,58 +1,58 @@ -package com.cognifide.cogboard.widget.type - -import com.cognifide.cogboard.CogboardConstants.ConnectionType -import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.CogboardConstants.RequestMethod -import io.vertx.core.buffer.Buffer -import io.vertx.core.eventbus.MessageConsumer -import io.vertx.core.json.JsonObject -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.mockito.Mockito.* - -class LogViewerTest: WidgetTestBase() { - - private lateinit var widget: LogViewerWidget - - override fun widgetName(): String { - return "LogViewerWidget" - } - - @BeforeEach - fun initForTest() { - super.init() - } - - @Test - fun `Expect Buffer consumer to be used when type is SSH`() { - val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer - `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) - - val config = initWidget() - .put(Props.LOG_SOURCE_TYPE, ConnectionType.SSH) - .put(Props.LOG_SOURCE, "192.168.0.1") - .put(Props.LOG_LINES, "5") - widget = LogViewerWidget(vertx, config, initService()) - - widget.start() - - verify(eventBus).consumer(eq(widget.eventBusAddress)) - } - - @Test - fun `Expect JsonObject consumer to be used when type is HTTP`() { - val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer - `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) - - val config = initWidget() - .put(Props.LOG_SOURCE_TYPE, ConnectionType.HTTP) - .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) - .put(Props.LOG_SOURCE, "192.168.0.1") - .put(Props.LOG_LINES, "5") - widget = LogViewerWidget(vertx, config, initService()) - - widget.start() - - verify(eventBus).consumer(eq(widget.eventBusAddress)) - } -} \ No newline at end of file +// package com.cognifide.cogboard.widget.type + +// import com.cognifide.cogboard.CogboardConstants.ConnectionType +// import com.cognifide.cogboard.CogboardConstants.Props +// import com.cognifide.cogboard.CogboardConstants.RequestMethod +// import io.vertx.core.buffer.Buffer +// import io.vertx.core.eventbus.MessageConsumer +// import io.vertx.core.json.JsonObject +// import org.junit.jupiter.api.BeforeEach +// import org.junit.jupiter.api.Test +// import org.mockito.Mockito.* + +// class LogViewerTest: WidgetTestBase() { + +// private lateinit var widget: LogViewerWidget + +// override fun widgetName(): String { +// return "LogViewerWidget" +// } + +// @BeforeEach +// fun initForTest() { +// super.init() +// } + +// @Test +// fun `Expect Buffer consumer to be used when type is SSH`() { +// val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer +// `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + +// val config = initWidget() +// .put(Props.LOG_SOURCE_TYPE, ConnectionType.SSH) +// .put(Props.LOG_SOURCE, "192.168.0.1") +// .put(Props.LOG_LINES, "5") +// widget = LogViewerWidget(vertx, config, initService()) + +// widget.start() + +// verify(eventBus).consumer(eq(widget.eventBusAddress)) +// } + +// @Test +// fun `Expect JsonObject consumer to be used when type is HTTP`() { +// val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer +// `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + +// val config = initWidget() +// .put(Props.LOG_SOURCE_TYPE, ConnectionType.HTTP) +// .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) +// .put(Props.LOG_SOURCE, "192.168.0.1") +// .put(Props.LOG_LINES, "5") +// widget = LogViewerWidget(vertx, config, initService()) + +// widget.start() + +// verify(eventBus).consumer(eq(widget.eventBusAddress)) +// } +// } \ No newline at end of file diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json index 9cdcfe54e..87cb09916 100644 --- a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json +++ b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json @@ -5,14 +5,16 @@ "label": "My Credentials 1", "user": "user1", "password": "password1", - "token": "token1" + "token": "token1", + "sshKey": "key1" }, { "id": "credentials2", "label": "My Credentials 2", "user": "user2", "password": "password2", - "token": "token2" + "token": "token2", + "sshKey": "key2" } ] } From 7178432254c4d11022571af67f86875f6e049bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 2 Nov 2021 20:38:03 +0100 Subject: [PATCH 039/226] Change text fo file --- .../widgets/dialogFields/FileTextInput.js | 72 +++++++++++++++++++ .../components/widgets/dialogFields/index.js | 3 +- .../components/widgets/dialogFields/styled.js | 33 ++++++++- 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js diff --git a/cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js b/cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js new file mode 100644 index 000000000..da018a07e --- /dev/null +++ b/cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; + +import { Button, InputLabel } from '@material-ui/core'; +import { StyledValidationMessages } from '../../WidgetForm/styled'; +import { + DeleteButton, + StyledHorizontalStack, + StyledLabel, + StyledVerticalStack +} from './styled'; + +const FileTextInput = ({ + error, + dataCy, + values, + label, + value, + onChange, + ...other +}) => { + const [filename, setFilename] = useState(''); + + const getFileContents = async e => { + e.preventDefault(); + const file = e.target.files[0]; + const reader = new FileReader(); + reader.onload = async e => { + const text = e.target.result; + let event = e; + event.target.value = text; + onChange(event); + alert(text); + console.log(text); + }; + reader.readAsText(file); + setFilename(file.name); + }; + + const deleteFile = e => { + e.preventDefault(); + setFilename(''); + let event = e; + event.target.value = ''; + onChange(event); + }; + + const fileInfo = + filename === '' ? null : ( + +

{filename}

+ deleteFile(e)}> + Delete + +
+ ); + + return ( + + SSH Key + + + {fileInfo} + + + + ); +}; + +export default FileTextInput; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index 083518142..52a8f313a 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -37,6 +37,7 @@ import RangeSlider from './RangeSlider'; import LinkListInput from './LinkListInput'; import ToDoListInput from './ToDoListinput'; import WidgetTypeField from './WidgetTypeField'; +import FileTextInput from './FileTextInput'; const dialogFields = { LabelField: { @@ -101,7 +102,7 @@ const dialogFields = { validator: () => string() }, SSHKeyField: { - component: MultilineTextInput, + component: FileTextInput, name: 'sshKey', label: 'SSH Private Key', validator: () => diff --git a/cogboard-webapp/src/components/widgets/dialogFields/styled.js b/cogboard-webapp/src/components/widgets/dialogFields/styled.js index 4d56781db..593893e84 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/styled.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/styled.js @@ -2,7 +2,7 @@ import styled from '@emotion/styled/macro'; import NumberInput from './NumberInput'; import IntegerInput from './IntegerInput'; import { COLORS } from '../../../constants'; -import { Box, Input, Fab, List, FormControl } from '@material-ui/core'; +import { Box, Input, Fab, List, FormControl, Button } from '@material-ui/core'; export const StyledNumberInput = styled(NumberInput)` flex-basis: calc(50% - 18px); @@ -152,3 +152,34 @@ export const StyledMultiLineWrapper = styled.div` flex: 1 0 auto; } `; + +export const StyledHorizontalStack = styled.div` + display: flex; + flex-direction: row; + gap: 12px; +`; + +export const StyledVerticalStack = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const StyledLabel = styled.p` + font-size: 1rem; + margin: 0; + color: rgba(255, 255, 255, 0.7); + transform: translate(0, 1.5px) scale(0.75); + transform-origin: top left; + font-weight: 400; + line-height: 1; + letter-spacing: 0.00938em; +`; + +export const DeleteButton = styled(Button)` + background-color: ${COLORS.RED}; + + &:hover { + background-color: ${COLORS.DARK_RED}; + } +`; From 622953c9231c6c5c27d61037b1f729a9f0ddc6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 2 Nov 2021 21:52:48 +0100 Subject: [PATCH 040/226] Add SSH Key passphrase --- .../config/controller/CredentialsController.kt | 1 + .../cognifide/cogboard/config/model/Credential.kt | 3 ++- .../src/components/CredentialForm/index.js | 6 ++++-- .../widgets/dialogFields/FileTextInput.js | 14 ++------------ .../src/components/widgets/dialogFields/index.js | 8 +++++++- .../cypress/fixtures/credentialsEndpoints.js | 1 + .../cypress-tests/cypress/support/credential.js | 14 ++++++++++++-- 7 files changed, 29 insertions(+), 18 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt index d2cdc3796..e9288c042 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/CredentialsController.kt @@ -38,6 +38,7 @@ class CredentialsController : AbstractVerticle() { private fun JsonObject.filterSensitiveData(): JsonObject { this.remove(Props.PASSWORD) this.remove(Props.SSH_KEY) + this.remove(Props.SSH_KEY_PASSPHRASE) return this } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt index ddb05261d..6727bf401 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Credential.kt @@ -6,5 +6,6 @@ data class Credential( val user: String, val password: String?, val token: String?, - val sshKey: String? + val sshKey: String?, + val sshKeyPassphrase: String? ) diff --git a/cogboard-webapp/src/components/CredentialForm/index.js b/cogboard-webapp/src/components/CredentialForm/index.js index 9c80150b1..bd8757fb6 100644 --- a/cogboard-webapp/src/components/CredentialForm/index.js +++ b/cogboard-webapp/src/components/CredentialForm/index.js @@ -23,7 +23,8 @@ const CredentialsForm = ({ 'PasswordField', 'PasswordConfirmationField', 'TokenField', - 'SSHKeyField' + 'SSHKeyField', + 'SSHKeyPassphraseField' ]; const constraints = { @@ -84,7 +85,8 @@ CredentialsForm.defaultProps = { password: '', confirmationPassword: '', token: '', - sshKey: '' + sshKey: '', + sshKeyPassphrase: '' }; export default CredentialsForm; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js b/cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js index da018a07e..0124f09af 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/FileTextInput.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { Button, InputLabel } from '@material-ui/core'; +import { Button } from '@material-ui/core'; import { StyledValidationMessages } from '../../WidgetForm/styled'; import { DeleteButton, @@ -9,15 +9,7 @@ import { StyledVerticalStack } from './styled'; -const FileTextInput = ({ - error, - dataCy, - values, - label, - value, - onChange, - ...other -}) => { +const FileTextInput = ({ error, dataCy, onChange }) => { const [filename, setFilename] = useState(''); const getFileContents = async e => { @@ -29,8 +21,6 @@ const FileTextInput = ({ let event = e; event.target.value = text; onChange(event); - alert(text); - console.log(text); }; reader.readAsText(file); setFilename(file.name); diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index 52a8f313a..c5891e036 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -111,11 +111,17 @@ const dialogFields = { message: vm.SSH_KEY_BEGIN, excludeEmptyString: true }) - .matches('\n-----END ([A-Z]{1,} )*PRIVATE KEY-----$', { + .matches('\n-----END ([A-Z]{1,} )*PRIVATE KEY-----\n$', { message: vm.SSH_KEY_END, excludeEmptyString: true }) }, + SSHKeyPassphraseField: { + component: PasswordInput, + name: 'sshKeyPassphrase', + label: 'SSH Private Key Passphrase', + validator: () => string() + }, PublicURL: { component: TextInput, name: 'publicUrl', diff --git a/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js b/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js index b4f058601..0f1194d8f 100644 --- a/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js +++ b/functional/cypress-tests/cypress/fixtures/credentialsEndpoints.js @@ -6,6 +6,7 @@ export const badCredentials = () => { user: 'xxxxxxxxxxxxxxxxxxxxxxxxxx', label: ' ', sshKey: 'xxx', + sshKeyPassphrase: '' }; }; diff --git a/functional/cypress-tests/cypress/support/credential.js b/functional/cypress-tests/cypress/support/credential.js index a757c8f96..e2db01bfd 100644 --- a/functional/cypress-tests/cypress/support/credential.js +++ b/functional/cypress-tests/cypress/support/credential.js @@ -69,6 +69,17 @@ class Credentials { return this; } + applySSHKeyPassphrase(config) { + if (config !== undefined) { + this.config = config; + } + cy.get('[data-cy="credential-form-auth-ssh-key-passphrase-input"]') + .clear() + .type(this.config.sshKeyPassphrase) + .blur(); + return this; + } + save() { cy.get('[data-cy="credential-form-submit-button"]').click(); return this; @@ -88,8 +99,7 @@ class Credentials { } assertErrorMessageVisible(message, dataCYName) { - cy - .contains(`[data-cy^="${dataCYName}"]`, message) + cy.contains(`[data-cy^="${dataCYName}"]`, message) .scrollIntoView() .should('is.visible'); return this; From c46718272fc4b75ecd13af75fbceed76540a60df Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 2 Nov 2021 22:46:39 +0100 Subject: [PATCH 041/226] Move template logs to backend --- .../cogboard/widget/type/LogViewerWidget.kt | 40 +++++++++ .../types/LogViewerWidget/LogList/index.js | 85 ++----------------- .../widgets/types/LogViewerWidget/index.js | 22 +++-- 3 files changed, 64 insertions(+), 83 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 82c7d1cf2..0888998b1 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -6,6 +6,7 @@ import com.cognifide.cogboard.widget.BaseWidget import io.vertx.core.Vertx import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject +import io.vertx.core.json.JsonArray class LogViewerWidget( vertx: Vertx, @@ -39,12 +40,51 @@ class LogViewerWidget( // return super.stop() // } + private val logs = createTemplateLogs(20) override fun updateState() { // if (address.isNotBlank()) { // connectionStrategy.sendRequest(address, config) // } else { // sendConfigurationError("Endpoint URL is blank") // } + + send(JsonObject().put("logs", logs)) + } + + private fun createTemplateLogs(n: Int): JsonArray { + val types = arrayOf("info", "error", "debug", "success", "warn") + val logs = JsonArray() + + for (i in 1..n) { + logs.add(createTemplateLog(types[i % types.size])) + } + return logs + } + + private fun createTemplateLog(type: String): JsonObject { + return JsonObject(""" + { + "type": "$type", + "date": "2021-04-22 14:08:37", + "additionalData": { + "ID": "123456", + "Type": "sys", + "IP address": "127.0.0.1", + "Port": "27017" + }, + "variableData": { + "template": ["Provider", "Message"], + "header": [ + "mongodb.log", + "Expected corresponding JSX closing tag for ." + ], + "description": [ + "provider desc", + "SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)" + ] + } + } + """) } // private fun handleResponse(response: Message<*>) { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 5ff41576a..16654925c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -12,28 +12,8 @@ import { import getGridTemplate from './helpers'; const testLogTemplate = ['Provider', 'Message']; -const testData = { - date: '2021-04-22 14:08:37', - additionalData: { - ID: '123456', - Type: 'sys', - 'IP address': '127.0.0.1', - Port: '27017' - }, - variableData: { - template: testLogTemplate, - header: [ - 'mongodb.log', - 'Expected corresponding JSX closing tag for .' - ], - description: [ - 'provider desc', - 'SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)' - ] - } -}; -export default function LogList() { +export default function LogList({ children }) { const theme = useTheme(); const VariableLogListHeader = () => ( @@ -54,61 +34,14 @@ export default function LogList() {
- {/* static presentation */} - - - - - - - - - - + {children.map(log => ( + + ))} ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 512f33285..4a54c0490 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,15 +1,23 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { shallowEqual, useSelector } from 'react-redux'; import { number, string } from 'prop-types'; import Toolbar from './Toolbar'; import LogList from './LogList'; import { Container } from './styled'; -const LogViewerWidget = () => ( - - - - -); +const LogViewerWidget = ({ id }) => { + const widgetData = useSelector( + ({ widgets }) => widgets.widgetsById[id], + shallowEqual + ); + useEffect(() => console.log(widgetData), [widgetData]); + return ( + + + {widgetData.content.logs} + + ); +}; LogViewerWidget.propTypes = { endpoint: string, From 7a341e74b16a780db612c80a4fd4d1457632f1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Wed, 3 Nov 2021 00:30:44 +0100 Subject: [PATCH 042/226] Adjustments for communication with front-end --- .../com/cognifide/cogboard/ssh/SSHClient.kt | 3 +- .../ConnectionStrategyFactory.kt | 35 ++++- .../SSHConnectionStrategy.kt | 3 +- .../cogboard/widget/type/LogViewerWidget.kt | 6 +- .../cognifide/cogboard/ssh/SSHClientTest.kt | 125 ------------------ .../cogboard/widget/type/LogViewerTest.kt | 13 +- .../com/cognifide/cogboard/ssh/id_rsa | 39 ------ 7 files changed, 46 insertions(+), 178 deletions(-) delete mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt delete mode 100644 cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 46e8467a3..433a6a097 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -42,11 +42,12 @@ class SSHClient : AbstractVerticle() { } fun tryToConnect(config: JsonObject) { - val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) + try { connect(config) } catch (e: JSchException) { LOGGER.error(e.message) + val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) vertx.eventBus().send(eventBusAddress, e) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt index 2e91d756f..cc92d9a85 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -5,20 +5,49 @@ import com.cognifide.cogboard.CogboardConstants.ConnectionType.Companion.SSH import com.cognifide.cogboard.CogboardConstants.Props import io.vertx.core.Vertx import io.vertx.core.json.JsonObject +import java.net.URI class ConnectionStrategyFactory( - private var vertx: Vertx, - props: JsonObject + config: JsonObject, + uri: String ) { + private val connectionType: String + private lateinit var vertx: Vertx - private val connectionType = props.getString(Props.LOG_SOURCE_TYPE) + init { + connectionType = determineConnectionType(uri, config) + } fun addVertxInstance(vertx: Vertx): ConnectionStrategyFactory { this.vertx = vertx return this } + private fun determineConnectionType(uri: String, config: JsonObject): String { + val url = URI.create(uri) + return when (url.scheme) { + "http", "https" -> HTTP + "ssh" -> { + prepareSshConfig(url, config) + SSH + } + else -> { + throw UnknownConnectionTypeException("Unknown strategy type") + } + } + } + + private fun prepareSshConfig(uri: URI, config: JsonObject) { + config.put(Props.SSH_HOST, uri.host) + uri.port.let { + if (it != -1) config.put(Props.SSH_PORT, uri.port) + } + } + fun build(): ConnectionStrategy { + if (!::vertx.isInitialized) { + throw RuntimeException("Vertx instance not passed to builder") + } return when (connectionType) { HTTP -> HttpConnectionStrategy(vertx) SSH -> SSHConnectionStrategy(vertx) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index c88316796..1be556563 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -23,8 +23,7 @@ open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { private fun prepareConfig(config: JsonObject): JsonObject { val tmpConfig = prepareConfigLines(config = config, - Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_HOST, Props.SSH_KEY, Props.SSH_PORT, - Props.LOG_FILE_PATH, Props.LOG_LINES, Props.SSH_KEY_PASSPHRASE + Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE ) tmpConfig.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 438e3e290..16392012e 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -16,9 +16,7 @@ class LogViewerWidget( config: JsonObject, serv: BoardsConfigService ) : BaseWidget(vertx, config, serv) { - private val address = config.getString(Props.LOG_SOURCE) - private val lines = config.getString(Props.LOG_LINES) - private val connectionType = config.getString(Props.LOG_SOURCE_TYPE) + private val address = config.endpointProp(Props.URL) private var consumer: MessageConsumer<*>? = null private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() @@ -65,7 +63,7 @@ class LogViewerWidget( } private fun determineConnectionStrategy() = - ConnectionStrategyFactory(vertx, config) + ConnectionStrategyFactory(config, address) .addVertxInstance(vertx) .build() } diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt deleted file mode 100644 index 2c2605577..000000000 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/ssh/SSHClientTest.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.cognifide.cogboard.ssh - -import com.cognifide.cogboard.CogboardConstants -import com.cognifide.cogboard.ssh.auth.AuthenticationType -import com.jcraft.jsch.JSchException -import io.vertx.core.Vertx -import io.vertx.core.buffer.Buffer -import io.vertx.core.eventbus.EventBus -import io.vertx.core.json.Json -import io.vertx.core.json.JsonObject -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.* -import org.mockito.Captor -import org.mockito.Mock -import org.mockito.Mockito.* -import org.mockito.MockitoAnnotations.initMocks -import org.mockito.junit.jupiter.MockitoExtension - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@ExtendWith(MockitoExtension::class) -class SSHClientTest { - private lateinit var sshClient: SSHClient - - @Captor - private lateinit var bufferCaptor: ArgumentCaptor - - @Captor - private lateinit var exceptionCaptor: ArgumentCaptor - - private lateinit var config: JsonObject - - @Mock - lateinit var vertx: Vertx - - @Mock - lateinit var eventBus: EventBus - - @BeforeEach - fun init() { - sshClient = SSHClient() - initMocks(this) - `when`(vertx.eventBus()).thenReturn(eventBus) - sshClient.init(vertx, null) - } - - private fun generateConfig(authTypes: Set) { - config = JsonObject() - .put(CogboardConstants.Props.USER, "mock") - .put(CogboardConstants.Props.PASSWORD, "TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E") - .put(CogboardConstants.Props.TOKEN, "TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E") - .put(CogboardConstants.Props.SSH_KEY, SSHClientTest::class.java.getResource( - "/com/cognifide/cogboard/ssh/id_rsa").path) - .put(CogboardConstants.Props.SSH_HOST, "150.254.30.120") - .put(CogboardConstants.Props.LOG_FILE_PATH, "/home/mock/example.txt") - .put(CogboardConstants.Props.LOG_LINES, "1") - .put(CogboardConstants.Props.AUTHENTICATION_TYPES, Json.encode(authTypes)) - .put(CogboardConstants.Props.EVENT_ADDRESS, "ssh.test.address") - } - - @Test - fun `Executing commands with user+password authentication succeeds`() { - generateConfig(setOf(AuthenticationType.BASIC)) - - sshClient.tryToConnect(config) - val (result, exception) = captureWhatIsSent(eventBus, bufferCaptor, exceptionCaptor) - - result?.let { - assertEquals("19:28:11.445 [vert.x-eventloop-thread-2] " + - "ERROR c.cognifide.cogboard.http.HttpClient - Connection was closed\n", - it.getString(0, it.length()) - ) - } - assert(exception == null) - } - - @Test - fun `Executing commands using key authentication succeeds`() { - generateConfig(setOf(AuthenticationType.SSH_KEY)) - - sshClient.tryToConnect(config) - val (result, exception) = captureWhatIsSent(eventBus, bufferCaptor, exceptionCaptor) - - result?.let { - assertEquals("19:28:11.445 [vert.x-eventloop-thread-2] " + - "ERROR c.cognifide.cogboard.http.HttpClient - Connection was closed\n", - it.getString(0, it.length()) - ) - } - assert(exception == null) - } - - @Test - fun `Executing commands with wrong credentials fails`() { - generateConfig(setOf(AuthenticationType.BASIC)) - config.remove(CogboardConstants.Props.PASSWORD) - config.put(CogboardConstants.Props.PASSWORD, "wrong") - - sshClient.tryToConnect(config) - - val (result, exception) = captureWhatIsSent(eventBus, bufferCaptor, exceptionCaptor) - - assert(result == null) - assert(exception is JSchException) - } - - private fun captureWhatIsSent(eventBus: EventBus, - bufferCaptor: ArgumentCaptor, - exceptionCaptor: ArgumentCaptor - ): Pair{ - return try { - verify(eventBus).send(eq("ssh.test.address"), bufferCaptor.capture()) - verify(eventBus, times(0)).send(eq("ssh.test.address"), any(Exception::class.java)) - Pair(bufferCaptor.value, null) - } catch (e: Throwable) { - verify(eventBus, times(0)).send(eq("ssh.test.address"), any(Buffer::class.java)) - verify(eventBus).send(eq("ssh.test.address"), exceptionCaptor.capture()) - Pair(null, exceptionCaptor.value) - } - } -} \ No newline at end of file diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt index 896b60b27..8afc13e8c 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt @@ -28,9 +28,10 @@ class LogViewerTest: WidgetTestBase() { val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + val endpoint = mockEndpointData("ssh") + val config = initWidget() - .put(Props.LOG_SOURCE_TYPE, ConnectionType.SSH) - .put(Props.LOG_SOURCE, "192.168.0.1") + .put(Props.ENDPOINT_LOADED, endpoint) .put(Props.LOG_LINES, "5") widget = LogViewerWidget(vertx, config, initService()) @@ -43,11 +44,12 @@ class LogViewerTest: WidgetTestBase() { fun `Expect JsonObject consumer to be used when type is HTTP`() { val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) + + val endpoint = mockEndpointData("http") val config = initWidget() - .put(Props.LOG_SOURCE_TYPE, ConnectionType.HTTP) .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) - .put(Props.LOG_SOURCE, "192.168.0.1") + .put(Props.ENDPOINT_LOADED, endpoint) .put(Props.LOG_LINES, "5") widget = LogViewerWidget(vertx, config, initService()) @@ -55,4 +57,7 @@ class LogViewerTest: WidgetTestBase() { verify(eventBus).consumer(eq(widget.eventBusAddress)) } + + private fun mockEndpointData(protocol: String): JsonObject = + JsonObject(mapOf(Pair(Props.URL, "$protocol://192.168.0.1"))) } \ No newline at end of file diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa b/cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa deleted file mode 100644 index 2d77f8a17..000000000 --- a/cogboard-app/src/test/resources/com/cognifide/cogboard/ssh/id_rsa +++ /dev/null @@ -1,39 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIG5AIBAAKCAYEApphpbfnbEh4ZFdnloNqz0P3IDnADldRaD9ATzskGdcdRebJX -7mRJ+f174wyfee8WUl5W74TNIMHOo2oHFLD+/qtSg8m+w5JttmTS02wVCw/vBgC/ -3WSrR71jScBTQkm34ZQW9cZvuB0iJO3OtnGS2PJSxe1E8UNsey7EP/oXWjomCCgx -50hgWWDdj4PNHyMQoOcq9bZWT1yjWppSuBgaVYKwZhZeUPrObbixKOxMKqxEqjul -ayepln3DyI2Mq+/darE2cKD3FBA9MI6rbyA6nf0ncWCrrPsv1TpLFzqP9q1T+zPP -igpI778BuVJUYEEOWWmPmquti1tkiiAwJzKTaDILDObfifgcd9eyyr4FzZoArwep -Un6+yECthy2VMhxbGsJXVAnYmC3/06MFVjNXLgTvQG/h2YsknkSKaBgVdqI5ireK -l1rxkmIBzjdqJ8QuJjR1TbSxjmsDNeNCxrskLfAL3ATRJGmdXQHv6GU1g3Lt8qjr -TF/MMdV//oM/NwxfAgMBAAECggGAS/PaxVgPh8APIcY5CdAMATFi3yo0iD/LW8A6 -96DxAAuCWuBzdG1myMHHlKlxn87gN2dpbUP3nYKeqiQx3D4h04vaT9lPzqxYtrpa -4Cc7pNJ74BnBX7eA23E97ibwDp27Zu30VdNFpgQqR/nfr5AyKhS4piJZt0FNGRAm -SyDDOtCd/EQPFGoL+1PNT5wAjbyX3TPngyTBTudmVm1bXzEl+Gxf7fsuvu7J5H46 -zvSEeUQW/iG5dhQcHk9yTmIyi0UMyipOv+e+K4ujPQYDTaGL1y2czUlnCGZkztql -DWVRI54YBfNDX0tawaPrIuL7BT1yZVGaT3w89FIckJqRNBCy9LeaSz3qphpuLtZ6 -jZLGByEIurQCGJHhcJfU/mm25uCc3FFiSWeCIQcIMgF0zlQtlKFBsLzIZrHBFY6Q -XL4sxS6/QpotQ4p2tF1RbG2e6RjnJzammk79U5hs7/9nb4IbSQcn0SDz9UpNQzOD -WENMMQbUtyz4JEWrQPfNpgGdyOlxAoHBANVgiUrX27l6f9yUYdET4LCA6yINVQM5 -628+78Ho1GrnZqs3F+bO+9+jjGgW1ousLsnbmeaLNvbm2tZJhKCy4SfwypYCZDpO -8QY1Pscg6Wd8gsrjyjq6j/FoVp9TrWs4tUHjbIv9YEtBXdeto2CCSb/uhlc6jUDj -tmY0OLf5fWKFrKmfIZSFT/mz8ZO5KFXILuO395ZG/tMLxsRVm1Rsj2xFzUOnt6hg -NEJxI8Qx/7MC69SNrdkt34Kj7JdfkXeJmQKBwQDH35kbAMExA+Gznm/eE89TyDLc -P5qbsP62rc9BqU8aTnwwsd2QGhU3qwGx/0TEL+mR77j7cyXs8Z8bhqoqjr/5IvkJ -Bk8D18bNmu+QCeRs+kDUf1XdUzCF5uH6QezpnntD7KnUf+aJ+Byf6qp0WdLTyi/m -eJq2OqMqeDJnapY7wC9FpycD2Qw3eh47CFfIPFb+cRjsmIPaG28PHSmcU+uvZy9Z -t5wo7z+AmkBvcBBlPGORmH/4EUbAzNitCLEPMLcCgcAYngeY6+h8sKZZw4C5h2qP -0n/OMO2S7ra74e31WDBRmRreO//08AJdHdhQFfpwqM/JGB3cXEleGOo8eMTlaV3S -ptQolGQN2heymKgCf0iOWO4aDEfDmMyMlHYR8ax7R1KaJGvchsH6TLNP88d6le1+ -SD2ViLrNNrhPeEzm5NDTcgqmkyZjpTHB5EHFwkHmC69lNE9LNiumG6C1/A5sog21 -9yOkX5Avy9GbrMPsmbwfYgHlRuc8vC8iOllMaYk12lkCgcEAnrqVznoE8rYQciLq -2ESHLBQbQApK8EXZop3ZsF2S8mNOASXfvObZ3sGxsPf2Vo9uQHVORHKntcoU/zks -vmtrTaFWgLI/4d9khDf89DjG7Ty4a4lMWV4NdOlsoN/mE4nGfqTeImIIZZ1iriUg -hRIoegVhkPhKdVmP5tRZwwNEl+MCJ6CdPVMLfqs+gscXX2977slGb+6XF4kd47TX -LM0AqOWbFvbXzzJhDfJyJY2rl5ojwKaJSin+NHL5yEDUzCl1AoHBAK85ZTR0CmlE -jV4OrsBk937oDysYMYH6IYAUQ4xX4jCu5nbHi6xIpAuha2Xh0vWCr7xoguxDE67j -z6NeE2s3wsjK5I9tvAxwvpnSUvMPFVojlxrPQLHcRh3VmAq3kYtF2HrmJx+lsmDD -mLZLHgj4lqcWlhME4ZsENrLBoUtwx9NEvTjJoNGdeU1TsDSUx/a5DGleeqLHjejS -SoGobi0iGwCCe/gfAyJ4zgtYe3ie6yJRcsESHuCfrk6ET0vodep/nA== ------END RSA PRIVATE KEY----- From a1c7982daabb4484131a11ce3c596b5d7ee7de56 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 3 Nov 2021 12:43:37 +0100 Subject: [PATCH 043/226] Merge fixes --- .../cogboard/widget/type/LogViewerWidget.kt | 12 ++++-------- .../widgets/types/LogViewerWidget/LogList/index.js | 4 ++-- .../widgets/types/LogViewerWidget/index.js | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 7f7c160d6..40c4b5fd8 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -3,7 +3,11 @@ package com.cognifide.cogboard.widget.type import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.widget.BaseWidget +import com.cognifide.cogboard.widget.Widget +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import io.vertx.core.Vertx +import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject import io.vertx.core.json.JsonArray @@ -14,14 +18,6 @@ class LogViewerWidget( serv: BoardsConfigService ) : BaseWidget(vertx, config, serv) { private val address = config.endpointProp(Props.URL) - - // private val user: String = config.endpointProp(Props.USER) - // private val password: String = config.endpointProp(Props.PASSWORD) - // private val token: String = config.endpointProp(Props.TOKEN) - private val sshKey: String = config.endpointProp(Props.SSH_KEY) - private val url: String = config.endpointProp(Props.URL) - private val publicUrl: String = config.endpointProp(Props.PUBLIC_URL).ifBlank { url } - private var consumer: MessageConsumer<*>? = null private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 16654925c..d1a3514f7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -13,7 +13,7 @@ import getGridTemplate from './helpers'; const testLogTemplate = ['Provider', 'Message']; -export default function LogList({ children }) { +export default function LogList({ logs }) { const theme = useTheme(); const VariableLogListHeader = () => ( @@ -34,7 +34,7 @@ export default function LogList({ children }) {
- {children.map(log => ( + {logs?.map(log => ( { return ( - {widgetData.content.logs} + ); }; From 7eae0b5867459494e531a09229d65ec48d6b62f1 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 3 Nov 2021 13:19:04 +0100 Subject: [PATCH 044/226] Attempt to load logs from server --- .../cogboard/config/EndpointLoader.kt | 1 + .../com/cognifide/cogboard/ssh/SSHClient.kt | 5 +- .../cogboard/widget/type/LogViewerWidget.kt | 75 +++++++++---------- .../cognifide/cogboard/config/EndpointTest.kt | 12 ++- .../cogboard/config/credentials-test.json | 6 +- .../widgets/types/LogViewerWidget/index.js | 2 +- 6 files changed, 57 insertions(+), 44 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt index 0165b1c71..bf4712cbe 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt @@ -32,6 +32,7 @@ class EndpointLoader( this.put(Props.PASSWORD, credentials.getString(Props.PASSWORD) ?: "") this.put(Props.TOKEN, credentials.getString(Props.TOKEN) ?: "") this.put(Props.SSH_KEY, credentials.getString(Props.SSH_KEY) ?: "") + this.put(Props.SSH_KEY_PASSPHRASE, credentials.getString(Props.SSH_KEY_PASSPHRASE) ?: "") } } return this diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 433a6a097..a98b80ba2 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -86,7 +86,10 @@ class SSHClient : AbstractVerticle() { private fun executeCommandAndSendResult(config: JsonObject) { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) val responseBuffer = Buffer.buffer() - responseBuffer.appendBytes(sshInputStream.readAllBytes()) + val tmpBuf = ByteArray(512) + while (sshInputStream.read(tmpBuf, 0, 512) != -1) { + responseBuffer.appendBytes(tmpBuf) + } vertx.eventBus().send(eventBusAddress, responseBuffer) channel.disconnect() session.disconnect() diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt index 40c4b5fd8..66853fd8b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt @@ -10,7 +10,6 @@ import io.vertx.core.Vertx import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject -import io.vertx.core.json.JsonArray class LogViewerWidget( vertx: Vertx, @@ -34,7 +33,7 @@ class LogViewerWidget( return super.stop() } - private val logs = createTemplateLogs(20) + // private val logs = createTemplateLogs(20) override fun updateState() { if (address.isNotBlank()) { connectionStrategy.sendRequest(address, config) @@ -42,44 +41,44 @@ class LogViewerWidget( sendConfigurationError("Endpoint URL is blank") } - send(JsonObject().put("logs", logs)) + // send(JsonObject().put("logs", logs)) } - private fun createTemplateLogs(n: Int): JsonArray { - val types = arrayOf("info", "error", "debug", "success", "warn") - val logs = JsonArray() + // private fun createTemplateLogs(n: Int): JsonArray { + // val types = arrayOf("info", "error", "debug", "success", "warn") + // val logs = JsonArray() - for (i in 1..n) { - logs.add(createTemplateLog(types[i % types.size])) - } - return logs - } + // for (i in 1..n) { + // logs.add(createTemplateLog(types[i % types.size])) + // } + // return logs + // } - private fun createTemplateLog(type: String): JsonObject { - return JsonObject(""" - { - "type": "$type", - "date": "2021-04-22 14:08:37", - "additionalData": { - "ID": "123456", - "Type": "sys", - "IP address": "127.0.0.1", - "Port": "27017" - }, - "variableData": { - "template": ["Provider", "Message"], - "header": [ - "mongodb.log", - "Expected corresponding JSX closing tag for ." - ], - "description": [ - "provider desc", - "SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)" - ] - } - } - """) - } + // private fun createTemplateLog(type: String): JsonObject { + // return JsonObject(""" + // { + // "type": "$type", + // "date": "2021-04-22 14:08:37", + // "additionalData": { + // "ID": "123456", + // "Type": "sys", + // "IP address": "127.0.0.1", + // "Port": "27017" + // }, + // "variableData": { + // "template": ["Provider", "Message"], + // "header": [ + // "mongodb.log", + // "Expected corresponding JSX closing tag for ." + // ], + // "description": [ + // "provider desc", + // "SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6) SyntaxError: /Users/celmer/Documents/js/cogboard/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js: Expected corresponding JSX closing tag for . (21:6)" + // ] + // } + // } + // """) + // } private fun handleResponse(response: Message<*>) { val responseBody = response.body() @@ -96,9 +95,9 @@ class LogViewerWidget( } } - private fun prepareLogs(logs: String): String { + private fun prepareLogs(logs: String): JsonObject { // TODO - return logs + return JsonObject().put("logs", logs) } private fun determineConnectionStrategy() = diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt index fbfa08da7..8bcb65644 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/EndpointTest.kt @@ -38,11 +38,14 @@ internal class EndpointTest { assert(validEndpoint.containsKey("password")) assert(validEndpoint.containsKey("token")) assert(validEndpoint.containsKey("sshKey")) + assert(validEndpoint.containsKey("sshKeyPassphrase")) assert(invalidEndpoint.containsKey("user")) assert(invalidEndpoint.containsKey("password")) assert(invalidEndpoint.containsKey("token")) assert(invalidEndpoint.containsKey("sshKey")) + assert(invalidEndpoint.containsKey("sshKeyPassphrase")) + } @Test @@ -51,6 +54,8 @@ internal class EndpointTest { assertEquals("password1", validEndpoint.getString("password")) assertEquals("token1", validEndpoint.getString("token")) assertEquals("key1", validEndpoint.getString("sshKey")) + assertEquals("pass1", validEndpoint.getString("sshKeyPassphrase")) + } @Test @@ -59,6 +64,7 @@ internal class EndpointTest { assertEquals("", invalidEndpoint.getString("password")) assertEquals("", invalidEndpoint.getString("token")) assertEquals("", invalidEndpoint.getString("sshKey")) + assertEquals("", invalidEndpoint.getString("sshKeyPassphrase")) } @Test @@ -72,7 +78,8 @@ internal class EndpointTest { "user" : "user1", "password" : "password1", "token" : "token1", - "sshKey": "key1" + "sshKey": "key1", + "sshKeyPassphrase" : "pass1" } """) @@ -89,7 +96,8 @@ internal class EndpointTest { "user" : "", "password" : "", "token" : "", - "sshKey" : "" + "sshKey" : "", + "sshKeyPassphrase" : "" } """) diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json index 87cb09916..b5b431094 100644 --- a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json +++ b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json @@ -6,7 +6,8 @@ "user": "user1", "password": "password1", "token": "token1", - "sshKey": "key1" + "sshKey": "key1", + "sshKeyPassphrase": "pass1" }, { "id": "credentials2", @@ -14,7 +15,8 @@ "user": "user2", "password": "password2", "token": "token2", - "sshKey": "key2" + "sshKey": "key2", + "sshKeyPassphrase": "pass2" } ] } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index b32928609..39cdffda3 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -14,7 +14,7 @@ const LogViewerWidget = ({ id }) => { return ( - + {/* */} ); }; From 52170d23f8bb5c8f780bf502b626008755bde3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sun, 7 Nov 2021 04:47:24 +0100 Subject: [PATCH 045/226] Successfully sending parsed data to front-end --- cogboard-app/build.gradle.kts | 1 + .../cognifide/cogboard/CogboardConstants.kt | 5 +- .../com/cognifide/cogboard/ssh/SSHClient.kt | 43 +++++++++---- .../cogboard/ssh/auth/SSHAuthData.kt | 4 +- .../cognifide/cogboard/widget/WidgetIndex.kt | 2 +- .../connectionStrategy/ConnectionStrategy.kt | 2 +- .../ConnectionStrategyFactory.kt | 30 +++++++-- .../HttpConnectionStrategy.kt | 5 +- .../SSHConnectionStrategy.kt | 4 +- .../type/{ => logviewer}/LogViewerWidget.kt | 22 +++++-- .../logviewer/logparser/LogParserStrategy.kt | 19 ++++++ .../logparser/LogParserStrategyFactory.kt | 16 +++++ .../logparser/MockLogParserStrategy.kt | 29 +++++++++ .../type/logviewer/logparser/ParsedLog.kt | 63 +++++++++++++++++++ .../type/{ => logviewer}/LogViewerTest.kt | 4 +- .../logparser/MockLogParserStrategyTest.kt | 30 +++++++++ 16 files changed, 244 insertions(+), 35 deletions(-) rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/{ => logviewer}/LogViewerWidget.kt (68%) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt rename cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/{ => logviewer}/LogViewerTest.kt (94%) create mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt diff --git a/cogboard-app/build.gradle.kts b/cogboard-app/build.gradle.kts index f1ce38a03..48a4bd342 100644 --- a/cogboard-app/build.gradle.kts +++ b/cogboard-app/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.0") implementation(kotlin("stdlib-jdk8")) implementation("com.jcraft:jsch:0.1.55") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2") testImplementation("org.assertj:assertj-core:3.12.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.4.2") diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index c7ed23833..c7f607334 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -45,11 +45,8 @@ class CogboardConstants { const val SSH_KEY_PASSPHRASE = "sshKeyPassphrase" const val URL = "url" - const val LOG_SOURCE = "logSource" - const val LOG_SOURCE_TYPE = "logSourceType" const val LOG_REQUEST_TYPE = "logRequestType" - const val LOG_LINES = "logLines" - const val LOG_FILE_PATH = "logFilePath" + const val LOG_LINES = "logLinesField" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" const val USER = "user" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 433a6a097..695a1523e 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -13,6 +13,10 @@ import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject import io.vertx.core.logging.Logger import io.vertx.core.logging.LoggerFactory +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import java.io.InputStream class SSHClient : AbstractVerticle() { @@ -42,27 +46,31 @@ class SSHClient : AbstractVerticle() { } fun tryToConnect(config: JsonObject) { - - try { - connect(config) - } catch (e: JSchException) { - LOGGER.error(e.message) - val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) - vertx.eventBus().send(eventBusAddress, e) + LOGGER.info(config) + coroutineScope.launch { + try { + connect(config) + } catch (e: JSchException) { + LOGGER.error(e.message) + val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) + vertx.eventBus().send(eventBusAddress, e.message) + } } } - private fun connect(config: JsonObject) { + private suspend fun connect(config: JsonObject) { val authData = SSHAuthData(config) createSSHChannel(authData) executeCommandAndSendResult(config) } - private fun createSSHChannel(authData: SSHAuthData) { + private suspend fun createSSHChannel(authData: SSHAuthData) { with(authData) { initSSHSession(authData) if (session.isConnected) { createChannel(createCommand()) + } else { + LOGGER.error("Failed to connect to ${authData.host}") } } } @@ -85,14 +93,27 @@ class SSHClient : AbstractVerticle() { private fun executeCommandAndSendResult(config: JsonObject) { val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) - val responseBuffer = Buffer.buffer() - responseBuffer.appendBytes(sshInputStream.readAllBytes()) + val responseBuffer = readResponse() vertx.eventBus().send(eventBusAddress, responseBuffer) channel.disconnect() session.disconnect() } + private fun readResponse(): Buffer { + val responseBuffer = Buffer.buffer() + val tmpBuf = ByteArray(512) + var readBytes = sshInputStream.read(tmpBuf, 0, 512) + while (readBytes != -1) { + responseBuffer.appendBytes(tmpBuf, 0, readBytes) + readBytes = sshInputStream.read(tmpBuf, 0, 512) + } + + return responseBuffer + } + companion object { val LOGGER: Logger = LoggerFactory.getLogger(SSHClient::class.java) + + val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index ee0d41ea8..26ef790f3 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -39,8 +39,8 @@ class SSHAuthData(private val config: JsonObject) { } fun createCommand(): String { - val logLines = config.getString(CogboardConstants.Props.LOG_LINES) ?: "0" - val logFilePath = config.getString(CogboardConstants.Props.LOG_FILE_PATH) ?: "" + val logLines = config.getInteger(CogboardConstants.Props.LOG_LINES) ?: 0 + val logFilePath = config.getString(CogboardConstants.Props.PATH) ?: "" return "cat $logFilePath | tail -$logLines" } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt index e057a29be..ee5235c1b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/WidgetIndex.kt @@ -20,7 +20,7 @@ import com.cognifide.cogboard.widget.type.WorldClockWidget import com.cognifide.cogboard.widget.type.randompicker.RandomPickerWidget import com.cognifide.cogboard.widget.type.sonarqube.SonarQubeWidget import com.cognifide.cogboard.widget.type.zabbix.ZabbixWidget -import com.cognifide.cogboard.widget.type.LogViewerWidget +import com.cognifide.cogboard.widget.type.logviewer.LogViewerWidget import io.vertx.core.Vertx import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index fcf425be2..dcafef062 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -6,7 +6,7 @@ import io.vertx.core.Vertx import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject -abstract class ConnectionStrategy(protected val vertx: Vertx) { +abstract class ConnectionStrategy(protected val vertx: Vertx, protected val eventBusAddress: String) { protected fun JsonObject.endpointProp(prop: String): String { return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt index cc92d9a85..b734cb998 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt @@ -13,6 +13,7 @@ class ConnectionStrategyFactory( ) { private val connectionType: String private lateinit var vertx: Vertx + private lateinit var eventBusAddress: String init { connectionType = determineConnectionType(uri, config) @@ -23,6 +24,11 @@ class ConnectionStrategyFactory( return this } + fun addEventBusAddress(eventBusAddress: String): ConnectionStrategyFactory { + this.eventBusAddress = eventBusAddress + return this + } + private fun determineConnectionType(uri: String, config: JsonObject): String { val url = URI.create(uri) return when (url.scheme) { @@ -44,13 +50,23 @@ class ConnectionStrategyFactory( } } - fun build(): ConnectionStrategy { - if (!::vertx.isInitialized) { - throw RuntimeException("Vertx instance not passed to builder") + fun checkRequiredParameters() { + var message = "" + when { + !::vertx.isInitialized -> message = "Vertx instance not passed to builder" + !::eventBusAddress.isInitialized -> message = "Eventbus address not passed to builder" + } + + if (message.isNotBlank()) { + throw MissingBuilderParametersException(message) } + } + + fun build(): ConnectionStrategy { + checkRequiredParameters() return when (connectionType) { - HTTP -> HttpConnectionStrategy(vertx) - SSH -> SSHConnectionStrategy(vertx) + HTTP -> HttpConnectionStrategy(vertx, eventBusAddress) + SSH -> SSHConnectionStrategy(vertx, eventBusAddress) else -> throw UnknownConnectionTypeException("Unknown strategy type") } } @@ -59,3 +75,7 @@ class ConnectionStrategyFactory( class UnknownConnectionTypeException( message: String? ) : RuntimeException(message) + +class MissingBuilderParametersException( + message: String? +) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt index f7cff2891..c4dc5500a 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt @@ -11,7 +11,8 @@ import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.Json import io.vertx.core.json.JsonObject -class HttpConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { +class HttpConnectionStrategy(vertx: Vertx, eventBusAddress: String) : + ConnectionStrategy(vertx, eventBusAddress) { override fun sendRequest(address: String, arguments: JsonObject) { when (arguments.getString(Props.LOG_REQUEST_TYPE, "")) { GET -> vertx.eventBus().send(Event.HTTP_GET, getProps(arguments)) @@ -30,7 +31,7 @@ class HttpConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { private fun basicProps(props: JsonObject): JsonObject = JsonObject() .put(Props.URL, props.endpointProp(Props.URL)) - .put(Props.EVENT_ADDRESS, props.endpointProp(Props.EVENT_ADDRESS)) + .put(Props.EVENT_ADDRESS, eventBusAddress) .put(Props.USER, props.endpointProp(Props.USER)) .put(Props.PASSWORD, props.endpointProp(Props.PASSWORD)) .put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index 1be556563..bd1805f9d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -9,7 +9,8 @@ import io.vertx.core.json.Json import io.vertx.core.json.JsonObject import java.nio.charset.Charset -open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { +open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : + ConnectionStrategy(vertx, eventBusAddress) { override fun sendRequest(address: String, arguments: JsonObject) { val config = prepareConfig(arguments) vertx.eventBus().send(CogboardConstants.Event.SSH_COMMAND, config) @@ -27,6 +28,7 @@ open class SSHConnectionStrategy(vertx: Vertx) : ConnectionStrategy(vertx) { ) tmpConfig.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + tmpConfig.put(Props.EVENT_ADDRESS, eventBusAddress) return tmpConfig } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt similarity index 68% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 16392012e..e8374cff1 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -1,4 +1,4 @@ -package com.cognifide.cogboard.widget.type +package com.cognifide.cogboard.widget.type.logviewer import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService @@ -6,6 +6,8 @@ import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory +import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategy +import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategyFactory import io.vertx.core.Vertx import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer @@ -20,6 +22,7 @@ class LogViewerWidget( private var consumer: MessageConsumer<*>? = null private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() + private val logParsingStrategy: LogParserStrategy = determineLogParsingStrategy() override fun start(): Widget { consumer = connectionStrategy.getConsumer(eventBusAddress) @@ -57,13 +60,20 @@ class LogViewerWidget( } } - private fun prepareLogs(logs: String): String { - // TODO - return logs + private fun prepareLogs(logs: String): JsonObject { + val logLines = logs.split("\n") + return JsonObject().put("logs", logParsingStrategy.parseLines(logLines)) } private fun determineConnectionStrategy() = ConnectionStrategyFactory(config, address) - .addVertxInstance(vertx) - .build() + .addVertxInstance(vertx) + .addEventBusAddress(eventBusAddress) + .build() + + private fun determineLogParsingStrategy() = + LogParserStrategyFactory() + .build(LogParserStrategyFactory.MOCK) + /* temporary solution, we'll have to decide if we'll get information of log types from + front-end or if we'll determine it from the logs themselves */ } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt new file mode 100644 index 000000000..5685e51d3 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt @@ -0,0 +1,19 @@ +package com.cognifide.cogboard.widget.type.logviewer.logparser + +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject + +abstract class LogParserStrategy { + fun parseLines(logLines: Collection): JsonArray { + val resultArray = JsonArray() + for (line in logLines) { + val parsedLine = parseLine(line) + resultArray.add(parsedLine) + } + return resultArray + } + + abstract fun parseLine(logLine: String): JsonObject +} + +class LogParsingException(message: String) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt new file mode 100644 index 000000000..889dcbeb6 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt @@ -0,0 +1,16 @@ +package com.cognifide.cogboard.widget.type.logviewer.logparser + +class LogParserStrategyFactory { + companion object { + const val MOCK = "mock" + } + + fun build(type: String): LogParserStrategy { + return when (type) { + MOCK -> MockLogParserStrategy() + else -> throw UnknownParserTypeException("Unknown strategy type") + } + } +} + +class UnknownParserTypeException(message: String) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt new file mode 100644 index 000000000..a0a9cb5eb --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt @@ -0,0 +1,29 @@ +package com.cognifide.cogboard.widget.type.logviewer.logparser + +import io.vertx.core.json.JsonObject +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.TYPE +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.DATE +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.PROVIDER +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.MESSAGE + +class MockLogParserStrategy : LogParserStrategy() { + private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""".trimMargin().toRegex() + + override fun parseLine(logLine: String): JsonObject { + val groups = regex.matchEntire(logLine.trim())?.groups + // ?: throw LogParsingException("Unable to parse line: $logLine") + + return createLogObject(groups) + } + + private fun createLogObject(groups: MatchGroupCollection?): JsonObject { + val mapOfCapturedValues = mutableMapOf() + mapOfCapturedValues[TYPE] = groups?.get(TYPE)?.value ?: "" + mapOfCapturedValues[DATE] = groups?.get(DATE)?.value ?: "" + mapOfCapturedValues[PROVIDER] = groups?.get(PROVIDER)?.value ?: "" + mapOfCapturedValues[MESSAGE] = groups?.get(MESSAGE)?.value ?: "" + + val parsedLog = ParsedLog(mapOfCapturedValues) + return parsedLog.parsedLogJson + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt new file mode 100644 index 000000000..d9de522f6 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt @@ -0,0 +1,63 @@ +package com.cognifide.cogboard.widget.type.logviewer.logparser + +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject + +class ParsedLog(values: Map) { + companion object { + const val DATE = "date" + const val TYPE = "type" + const val PROVIDER = "Provider" + const val MESSAGE = "Message" + const val TEMPLATE = "template" + const val HEADERS = "headers" + const val VARIABLE_DATA = "variableData" + const val DESCRIPTION = "description" + const val ADDITIONAL_DATA = "additionalData" + const val ID = "ID" + const val IP_ADDRESS = "IP address" + const val PORT = "Port" + } + + private val _parsedLogJson = JsonObject() + private val variableData = JsonObject() + + val parsedLogJson: JsonObject + get() { + _parsedLogJson.put(VARIABLE_DATA, variableData) + return _parsedLogJson + } + + init { + values[TYPE]?.let { _parsedLogJson.put(TYPE, it) } + values[DATE]?.let { _parsedLogJson.put(DATE, it) } + values[PROVIDER]?.let { addFieldToVariableData(PROVIDER, it) } + values[MESSAGE]?.let { addFieldToVariableData(MESSAGE, it) } + addAdditionalData() + } + + private fun addFieldToVariableData(template: String, value: String) { + val templateArray = variableData.getJsonArray(TEMPLATE, JsonArray()) + val headersArray = variableData.getJsonArray(HEADERS, JsonArray()) + val descriptionArray = variableData.getJsonArray(DESCRIPTION, JsonArray()) + + if (!templateArray.contains(template)) { + templateArray.add(template) + headersArray.add(value) + descriptionArray.add("No description") + } + variableData.put(TEMPLATE, templateArray) + variableData.put(HEADERS, headersArray) + variableData.put(DESCRIPTION, descriptionArray) + } + + private fun addAdditionalData() { + val additionalData = JsonObject() + additionalData.put(ID, "None") + additionalData.put(TYPE.capitalize(), "None") + additionalData.put(IP_ADDRESS, "None") + additionalData.put(PORT, "None") + + _parsedLogJson.put(ADDITIONAL_DATA, additionalData) + } +} diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerTest.kt similarity index 94% rename from cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt rename to cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerTest.kt index 8afc13e8c..50db30b4e 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/LogViewerTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerTest.kt @@ -1,8 +1,8 @@ -package com.cognifide.cogboard.widget.type +package com.cognifide.cogboard.widget.type.logviewer -import com.cognifide.cogboard.CogboardConstants.ConnectionType import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.CogboardConstants.RequestMethod +import com.cognifide.cogboard.widget.type.WidgetTestBase import io.vertx.core.buffer.Buffer import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt new file mode 100644 index 000000000..bb56a0fd9 --- /dev/null +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt @@ -0,0 +1,30 @@ +package com.cognifide.cogboard.widget.type.logviewer.logparser + +import org.junit.jupiter.api.Test +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.TYPE +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.DATE +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.VARIABLE_DATA +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.HEADERS +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.TEMPLATE +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.PROVIDER +import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.MESSAGE + +class MockLogParserStrategyTest { + private val sampleLog = "2021-11-06:22:40:25 *DEBUG* [FelixStartLevel] Integer lobortis. bibendum Nulla mi" + private val parser = MockLogParserStrategy() + + @Test + fun parseSampleLog() { + val output = parser.parseLine(sampleLog) + val variableData = output.getJsonObject(VARIABLE_DATA) + val template = variableData.getJsonArray(TEMPLATE) + val headers = variableData.getJsonArray(HEADERS) + + assert(output.getString(TYPE) == "DEBUG") + assert(output.getString(DATE) == "2021-11-06:22:40:25") + assert(template.getString(0) == PROVIDER) + assert(template.getString(1) == MESSAGE) + assert(headers.getString(0) == "FelixStartLevel") + assert(headers.getString(1) == "Integer lobortis. bibendum Nulla mi") + } +} \ No newline at end of file From 9650fc82dcc9c43b29ba1212512d1f7e48d62648 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 7 Nov 2021 21:33:04 +0100 Subject: [PATCH 046/226] Fix log structure typo --- .../widgets/types/LogViewerWidget/LogList/LogEntry.js | 8 ++++---- .../widgets/types/LogViewerWidget/LogList/index.js | 11 +++++------ .../components/widgets/types/LogViewerWidget/index.js | 4 +++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index 3999565ea..a56b7d487 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -34,10 +34,10 @@ export default function LogEntry({ type, date, additionalData, variableData }) { const VariablePart = ({ description }) => { const variableFieldsTemplate = getGridTemplate(variableData.template); - const data = description ? variableData.description : variableData.header; + const data = description ? variableData.description : variableData.headers; return ( - {data.map((text, index) => ( + {data?.map((text, index) => ( {text} ))} @@ -73,7 +73,7 @@ LogEntry.propTypes = { additionalData: objectOf(oneOfType([string, number, bool])), variableData: shape({ template: arrayOf(string).isRequired, - header: arrayOf(oneOfType([string, number, bool])).isRequired, + headers: arrayOf(oneOfType([string, number, bool])).isRequired, description: arrayOf(oneOfType([string, number, bool])).isRequired }) }; @@ -82,7 +82,7 @@ LogEntry.defaultProps = { type: 'info', variableData: { template: [], - header: [], + headers: [], description: [] }, additionalData: {} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index d1a3514f7..c85f9192a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -11,13 +11,11 @@ import { } from './styled'; import getGridTemplate from './helpers'; -const testLogTemplate = ['Provider', 'Message']; - -export default function LogList({ logs }) { +export default function LogList({ logs, template }) { const theme = useTheme(); const VariableLogListHeader = () => ( - - {testLogTemplate.map((name, index) => ( + + {template.map((name, index) => ( {name} ))} @@ -34,8 +32,9 @@ export default function LogList({ logs }) { - {logs?.map(log => ( + {logs?.map((log, index) => ( { shallowEqual ); useEffect(() => console.log(widgetData), [widgetData]); + + const logs = widgetData.content?.logs; return ( - {/* */} + {logs && } ); }; From 0d8f46fd77f9b1f11a6c72815988a3c3b98f6e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sun, 7 Nov 2021 21:51:11 +0100 Subject: [PATCH 047/226] Fixed sending blank log --- .../cogboard/widget/type/logviewer/LogViewerWidget.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 3ef904c0e..71b6697bd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -99,7 +99,8 @@ class LogViewerWidget( } private fun prepareLogs(logs: String): JsonObject { - val logLines = logs.split("\n") + var logLines = logs.split("\n") + logLines = logLines.filter { it.isNotEmpty() } return JsonObject().put("logs", logParsingStrategy.parseLines(logLines)) } From 86da1ef5372afccda5c604413d4181afb5675cc6 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 8 Nov 2021 18:24:42 +0100 Subject: [PATCH 048/226] Add logLevels config file --- .../types/LogViewerWidget/LogList/styled.js | 24 +++++++++---------- .../Toolbar/FilterPicker/index.js | 19 ++++++++------- .../types/LogViewerWidget/logLevels.js | 11 +++++++++ 3 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 2ff61248c..62669eaf6 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -1,6 +1,7 @@ import styled from '@emotion/styled/macro'; import { COLORS } from '../../../../../constants'; import { Typography, Accordion } from '@material-ui/core'; +import logLevels from '../logLevels'; export const Container = styled.div` max-height: 100%; @@ -36,23 +37,22 @@ export const ColumnTitle = styled(Typography)` `; export const Text = styled(Typography)(props => { - const getColor = type => - ({ - info: COLORS.WHITE, - success: COLORS.GREEN, - warn: COLORS.YELLOW, - error: COLORS.RED - }[type.toLowerCase()]); + let logTypeStyles = ``; + if (props.type) { + const logLevel = logLevels.find( + level => level.value === props.type?.toLowerCase() + ); + logTypeStyles = ` + font-weight: 500; + color: ${logLevel?.color || COLORS.WHITE}; + `; + } return ` line-height: 19px; font-size: 0.8rem; font-weight: 400; - ${props.type && - ` - font-weight: 500; - color: ${getColor(props.type)}; - `} + ${logTypeStyles} `; }); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index c999852c8..851dc8415 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -10,6 +10,7 @@ import { import { ScrollableBox } from './styled'; import ToolbarGroup from '../ToolbarGroup'; import { useState } from 'react'; +import logLevels from '../../logLevels'; const FilterPicker = () => { const handleDelete = name => { @@ -48,10 +49,11 @@ const FilterPicker = () => { )} > - DEBUG - INFO - WARN - ERROR + {logLevels.map((level, index) => ( + + {level.value.toUpperCase()} + + ))} @@ -64,10 +66,11 @@ const FilterPicker = () => { value={logLevel} onChange={e => setLogLevel(e.target.value)} > - DEBUG - INFO - WARN - ERROR + {logLevels.map((level, index) => ( + + {level.value.toUpperCase()} + + ))} + + + ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js new file mode 100644 index 000000000..56436121a --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { useToggle } from '../../../../../../../hooks'; + +import AppDialog from '../../../../../../AppDialog'; +import AddItem from '../../../../../../AddItem'; +import DeleteItem from '../../../../../../DeleteItem'; +import FilterForm from './FilterForm'; +import { + Button, + List, + ListItem, + ListItemText, + ListItemSecondaryAction +} from '@material-ui/core'; + +export default function AdvancedFiltersMenu() { + const [dialogOpened, openDialog, handleDialogClose] = useToggle(); + + const [filters, setFilters] = useState([{ id: 'filter1', label: 'filter1' }]); + const addFilter = filter => { + setFilters([...filters, filter]); + console.log('wywołany AddFilter'); + }; + + const renderListItems = (items, name, EditComponent, deleteAction) => + items.map(({ id, label }) => ( + + + + + {/* USES REDUX */} + {/* */} + + + )); + + return ( + <> + + + + {renderListItems( + filters, + 'filter', + () => 'E', + () => 'D' + )} + + + + + + + + ); +} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 851dc8415..56fb79077 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -1,4 +1,7 @@ import React from 'react'; +import { useState } from 'react'; +import logLevels from '../../logLevels'; + import { Button, Select, @@ -9,8 +12,7 @@ import { } from '@material-ui/core'; import { ScrollableBox } from './styled'; import ToolbarGroup from '../ToolbarGroup'; -import { useState } from 'react'; -import logLevels from '../../logLevels'; +import AdvancedFiltersMenu from './AdvancedFiltersMenu'; const FilterPicker = () => { const handleDelete = name => { @@ -73,9 +75,7 @@ const FilterPicker = () => { ))} - + ); }; From 1e6ab4457d8bac1172a0634a2062e4b49eb58a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sun, 14 Nov 2021 17:14:13 +0100 Subject: [PATCH 057/226] Saving ssh_key to file #416 --- .../cogboard/ssh/auth/SSHAuthData.kt | 25 ++++++++++++----- .../cogboard/ssh/auth/SSHKeyFileHelper.kt | 27 +++++++++++++++++++ .../connectionStrategy/ConnectionStrategy.kt | 2 +- .../SSHConnectionStrategy.kt | 7 ++++- 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHKeyFileHelper.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index 867adcb9a..31fc7f1cd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -8,12 +8,14 @@ import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject class SSHAuthData(private val config: JsonObject) { - val user = config.getString(Props.USER) ?: "" - val password = config.getString(Props.PASSWORD) ?: "" - val token = config.getString(Props.TOKEN) ?: "" - val key = config.getString(Props.SSH_KEY) ?: "" - val host = config.getString(Props.SSH_HOST) ?: "" - val port = config.getInteger(Props.SSH_PORT) ?: 22 + private val id = config.getString(Props.ID, "") + val user = config.getString(Props.USER, "") + val password = config.getString(Props.PASSWORD, "") + val token = config.getString(Props.TOKEN, "") + var key = config.getString(Props.SSH_KEY, "") + private set + val host = config.getString(Props.SSH_HOST, "") + val port = config.getInteger(Props.SSH_PORT, 22) val authenticationType = fromConfigAuthenticationType() private fun fromConfigAuthenticationType(): AuthenticationType { @@ -27,10 +29,19 @@ class SSHAuthData(private val config: JsonObject) { private fun hasAuthTypeCorrectCredentials(authType: AuthenticationType): Boolean = when { - authType == SSH_KEY && key.isNotBlank() -> true + authType == SSH_KEY && key.isNotBlank() -> { + prepareForSSHKeyUsage() + true + } else -> authType == BASIC && user.isNotBlank() && password.isNotBlank() } + private fun prepareForSSHKeyUsage() { + val fileHelper = SSHKeyFileHelper(id, key) + fileHelper.saveToFile() + config.put(Props.SSH_KEY, fileHelper.path) + } + fun getAuthenticationString(): String = when (authenticationType) { BASIC -> config.getString(Props.PASSWORD) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHKeyFileHelper.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHKeyFileHelper.kt new file mode 100644 index 000000000..51bfa378c --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHKeyFileHelper.kt @@ -0,0 +1,27 @@ +package com.cognifide.cogboard.ssh.auth + +import java.io.File + +class SSHKeyFileHelper( + private val id: String, + private val key: String +) { + private lateinit var file: File + lateinit var path: String + private set + + fun saveToFile() { + getOrCreateFile() + file.writeText(key) + } + + private fun getOrCreateFile() { + path = determineFilepath() + file = File(path) + path = file.absolutePath + } + + private fun determineFilepath() = + if (File("/data").exists()) "/data/$id" + else "${System.getProperty("user.dir")}/../mnt/$id" +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index dcafef062..9c65beb6b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -11,7 +11,7 @@ abstract class ConnectionStrategy(protected val vertx: Vertx, protected val even return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" } - protected open fun authenticationTypes(): Set { + protected open fun authenticationTypes(): Set { return setOf(AuthenticationType.BASIC) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index 0bd163ebc..eed2df9cd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -2,6 +2,7 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.ssh.auth.AuthenticationType import io.vertx.core.Vertx import io.vertx.core.buffer.Buffer import io.vertx.core.eventbus.MessageConsumer @@ -11,6 +12,10 @@ import java.nio.charset.Charset open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : ConnectionStrategy(vertx, eventBusAddress) { + override fun authenticationTypes(): Set { + return setOf(AuthenticationType.BASIC, AuthenticationType.SSH_KEY) + } + override fun sendRequest(address: String, arguments: JsonObject) { val config = prepareConfig(arguments) vertx.eventBus().send(CogboardConstants.Event.SSH_COMMAND, config) @@ -29,7 +34,7 @@ open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : private fun prepareConfig(config: JsonObject): JsonObject { val tmpConfig = prepareConfigLines(config = config, - Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE + Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE, Props.ID ) tmpConfig.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) From 3d1bff18c148ed6121ba4550e179d0ecfb2865a8 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 14 Nov 2021 23:34:02 +0100 Subject: [PATCH 058/226] Add edit option to advanced filters #389 --- .../AdvancedFiltersMenu/EditFilter.js | 46 +++++++++++++++++ .../AdvancedFiltersMenu/FilterForm.js | 8 +-- .../FilterPicker/AdvancedFiltersMenu/index.js | 50 +++++++++++++------ 3 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js new file mode 100644 index 000000000..c67c22fd6 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js @@ -0,0 +1,46 @@ +import React from 'react'; + +import { useToggle } from '../../../../../../../hooks'; + +import { IconButton, Tooltip } from '@material-ui/core'; +import { Build } from '@material-ui/icons'; +import AppDialog from '../../../../../../AppDialog'; +import FilterForm from './FilterForm'; + +const EditFilter = ({ id, filters, editAction }) => { + const [dialogOpened, openDialog, handleDialogClose] = useToggle(); + const filterData = filters.find(filter => filter.id === id); + + const handleSubmit = values => { + editAction({ id, values }); + handleDialogClose(); + }; + + return ( + <> + + + + + + + {filterData && ( + + )} + + + ); +}; + +export default EditFilter; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js index a10743646..dff82d566 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js @@ -5,13 +5,13 @@ import DynamicForm from '../../../../../../DynamicForm'; import { Button } from '@material-ui/core'; import CancelButton from '../../../../../../CancelButton'; -export default function FilterForm({ +const FilterForm = ({ filters, onSubmit, handleCancel, id, ...initialFormValues -}) { +}) => { const formFields = ['LabelField', 'RegExpField']; const constraints = { LabelField: { @@ -53,4 +53,6 @@ export default function FilterForm({ /> ); -} +}; + +export default FilterForm; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 56436121a..99d05ed0b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -1,10 +1,7 @@ import React, { useState } from 'react'; + import { useToggle } from '../../../../../../../hooks'; -import AppDialog from '../../../../../../AppDialog'; -import AddItem from '../../../../../../AddItem'; -import DeleteItem from '../../../../../../DeleteItem'; -import FilterForm from './FilterForm'; import { Button, List, @@ -12,22 +9,44 @@ import { ListItemText, ListItemSecondaryAction } from '@material-ui/core'; +import AppDialog from '../../../../../../AppDialog'; +import AddItem from '../../../../../../AddItem'; +import EditFilter from './EditFilter'; +import DeleteItem from '../../../../../../DeleteItem'; +import FilterForm from './FilterForm'; -export default function AdvancedFiltersMenu() { +const AdvancedFiltersMenu = () => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); - const [filters, setFilters] = useState([{ id: 'filter1', label: 'filter1' }]); - const addFilter = filter => { - setFilters([...filters, filter]); - console.log('wywołany AddFilter'); + const [filters, setFilters] = useState([]); + const addFilter = values => { + const maxId = filters.reduce((acc, { id }) => (id > acc ? id : acc), 0); + setFilters([...filters, { id: maxId + 1, ...values }]); }; - const renderListItems = (items, name, EditComponent, deleteAction) => + const editFilter = ({ id, values }) => { + setFilters( + filters.map(filter => { + if (filter.id === id) { + return { id, ...values }; + } + return filter; + }) + ); + }; + + const renderListItems = ( + items, + name, + EditComponent, + editAction, + deleteAction + ) => items.map(({ id, label }) => ( - + {/* USES REDUX */} {/* - 'E', + EditFilter, + editFilter, () => 'D' )} @@ -73,4 +93,6 @@ export default function AdvancedFiltersMenu() { ); -} +}; + +export default AdvancedFiltersMenu; From 6a98464102ab3f3479adfa25a56a2776c3885df8 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 15 Nov 2021 00:47:57 +0100 Subject: [PATCH 059/226] Add filtering by regExp #389 --- .../components/widgets/dialogFields/index.js | 2 +- .../types/LogViewerWidget/LogList/index.js | 30 +++++++++++++++++-- .../FilterPicker/AdvancedFiltersMenu/index.js | 4 +-- .../Toolbar/FilterPicker/index.js | 4 +-- .../types/LogViewerWidget/Toolbar/index.js | 4 +-- .../widgets/types/LogViewerWidget/index.js | 15 ++++++++-- 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index 4893f37b4..aeed0c34f 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -634,7 +634,7 @@ const dialogFields = { }, RegExpField: { component: MultilineTextInput, - name: 'logFilterRegExp', + name: 'regExp', label: 'Regular expression', validator: () => string() } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index c85f9192a..4be204ffa 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -11,8 +11,34 @@ import { } from './styled'; import getGridTemplate from './helpers'; -export default function LogList({ logs, template }) { +export default function LogList({ logs, template, regExpFilters }) { const theme = useTheme(); + + const filterByRegExp = (log, filters) => { + let result = true; + filters.forEach(({ regExp }) => { + let filterPassed = false; + const regExpObj = new RegExp(regExp); + const texts = [ + ...log.variableData.headers, + ...log.variableData.description + ]; + + texts.forEach(text => { + if (text.match(regExpObj)) { + filterPassed = true; + } + }); + + if (!filterPassed) { + result = false; + } + }); + return result; + }; + + const filteredLogs = logs?.filter(log => filterByRegExp(log, regExpFilters)); + const VariableLogListHeader = () => ( {template.map((name, index) => ( @@ -32,7 +58,7 @@ export default function LogList({ logs, template }) { - {logs?.map((log, index) => ( + {filteredLogs?.map((log, index) => ( { +const AdvancedFiltersMenu = ({ regExpFilters }) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); - const [filters, setFilters] = useState([]); + const [filters, setFilters] = regExpFilters; const addFilter = values => { const maxId = filters.reduce((acc, { id }) => (id > acc ? id : acc), 0); setFilters([...filters, { id: maxId + 1, ...values }]); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 56fb79077..c5f14dbbb 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -14,7 +14,7 @@ import { ScrollableBox } from './styled'; import ToolbarGroup from '../ToolbarGroup'; import AdvancedFiltersMenu from './AdvancedFiltersMenu'; -const FilterPicker = () => { +const FilterPicker = ({ regExpFilters }) => { const handleDelete = name => { setFilters(filters.filter(item => item !== name)); }; @@ -75,7 +75,7 @@ const FilterPicker = () => { ))} - + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index fcd445b61..11b1dd5a0 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -8,7 +8,7 @@ import GetAppIcon from '@material-ui/icons/GetApp'; import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; -const Toolbar = () => { +const Toolbar = ({ regExpFilters }) => { const theme = useTheme(); return ( @@ -17,7 +17,7 @@ const Toolbar = () => { - + diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index d7336f061..3a767b123 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import { number, string } from 'prop-types'; import Toolbar from './Toolbar'; @@ -6,6 +6,9 @@ import LogList from './LogList'; import { Container } from './styled'; const LogViewerWidget = ({ id }) => { + const [regExpFiltersGet, regExpFiltersSet] = useState([]); + const regExpFilters = [regExpFiltersGet, regExpFiltersSet]; + const widgetData = useSelector( ({ widgets }) => widgets.widgetsById[id], shallowEqual @@ -15,8 +18,14 @@ const LogViewerWidget = ({ id }) => { const logs = widgetData.content?.logs; return ( - - {logs && } + + {logs && ( + + )} ); }; From c43b56e63eeb956afa5c54e01426849cfef5a681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 17 Nov 2021 01:13:04 +0100 Subject: [PATCH 060/226] Add MockLogStorage --- .../com/cognifide/cogboard/logStorage/Log.kt | 79 +++------- .../logStorage/LogStorageConfiguration.kt | 22 +++ .../cogboard/logStorage/MongoLogStorage.kt | 142 +++++++++++++++++- .../com/cognifide/cogboard/ssh/SSHClient.kt | 72 ++++++++- .../connectionStrategy/ConnectionStrategy.kt | 13 ++ .../SSHConnectionStrategy.kt | 50 +++++- .../widget/type/logviewer/LogViewerWidget.kt | 4 + .../type/logviewer/logparser/LogParser.kt | 2 +- .../type/logviewer/logparser/MockLogParser.kt | 7 +- .../logparser/MockLogParserStrategy.kt | 4 +- 10 files changed, 320 insertions(+), 75 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt index 0dc874c82..f90a30f1d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt @@ -2,8 +2,6 @@ package main.kotlin.com.cognifide.cogboard.logStorage import org.bson.Document import org.bson.types.ObjectId -import java.util.* -import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject data class LogVariableData( @@ -31,77 +29,38 @@ data class LogVariableData( } } -data class LogAdditionalData( - val id: String, - val ipAddress: String, - val port: String, - val type: String -) { - private val map: Map - get() = mapOf( - "id" to id, - "ipAddress" to ipAddress, - "port" to port, - "type" to type) - - fun toDocument() = Document(map) - fun toJson() = JsonObject(map) - - companion object { - fun from(document: Document): LogAdditionalData? { - val id = document.getString("id") ?: return null - val ipAddress = document.getString("ipAddress") ?: return null - val port = document.getString("port") ?: return null - val type = document.getString("type") ?: return null - - return LogAdditionalData(id, ipAddress, port, type) - } - } -} - data class Log( + var id: ObjectId = ObjectId(), + var seq: Long = 0, var date: Long, var type: String, - var variableData: List, - var additionalData: LogAdditionalData? + var variableData: List ) { - fun toDocument(): Document { - val document = Document(mapOf( - "_id" to ObjectId(), - "date" to date, - "type" to type, - "variableData" to variableData.map { it.toDocument() }, - )) - - additionalData?.let { document.append("additionalData", it.toDocument()) } - return document - } - - fun toJson(): JsonObject { - val document = JsonObject(mapOf( - "_id" to ObjectId(), + private val map: Map + get() = mapOf( + "_id" to id, + "seq" to seq, "date" to date, "type" to type, - "variableData" to JsonArray(variableData.map { it.toJson() }), - )) - - additionalData?.let { document.put("additionalData", it.toDocument()) } - return document - } + "variableData" to variableData.map { it.toDocument() } + ) + fun toDocument() = Document(map) + fun toJson() = JsonObject(map) companion object { fun from(document: Document): Log? { - val date = document.getLong("date") ?: return null - val type = document.getString("type") ?: return null + val id = document.getObjectId("_id") + val seq = document.getLong("seq") + val date = document.getLong("date") + val type = document.getString("type") + + if(arrayOf(id, seq,date, type).contains(null)) { return null } + val variableData = document .getList("variableData", Document::class.java) ?.mapNotNull { it } ?.mapNotNull { LogVariableData.from(it) } ?: listOf() - val additionalData = document - .get("additionalData", Document::class.java) - ?.let { LogAdditionalData.from(it) } - - return Log(date, type, variableData, additionalData) + return Log(id, seq, date, type, variableData) } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt new file mode 100644 index 000000000..8f542e82f --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt @@ -0,0 +1,22 @@ +package com.cognifide.cogboard.logStorage + +import org.bson.Document + +data class LogStorageConfiguration(var lastLine: Int, var seq: Long) { + private val map: Map + get() = mapOf( + "_id" to CONFIG_ID, + "lastLine" to lastLine, + "seq" to seq + ) + fun toDocument() = Document(map) + + companion object { + const val CONFIG_ID: String = "config" + fun from(document: Document): LogStorageConfiguration? { + val lastLine = document.getInteger("lastLine") ?: return null + val seq = document.getLong("seq") ?: return null + return LogStorageConfiguration(lastLine, seq) + } + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt index 3893dd288..e25a6ce0b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt @@ -1,33 +1,163 @@ package com.cognifide.cogboard.logStorage +import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.http.auth.AuthenticationType +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy +import com.cognifide.cogboard.widget.connectionStrategy.SSHConnectionStrategyInt +import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParser import com.mongodb.client.MongoClient import com.mongodb.MongoException import com.mongodb.client.MongoClients import com.mongodb.client.MongoCollection +import com.mongodb.client.model.Filters.eq +import com.mongodb.client.model.ReplaceOptions +import io.vertx.core.AbstractVerticle +import io.vertx.core.json.Json +import io.vertx.core.json.JsonObject import org.bson.Document +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers -class MongoLogStorage { +class MongoLogStorage(private val connection: ConnectionStrategy, private val parser: LogParser) : AbstractVerticle() { + private var connectionNew = SSHConnectionStrategyInt() private fun getClient(): MongoClient? { if (client != null) { return client } try { client = MongoClients.create("mongodb://root:root@mongo:27017/") + return client } catch (exception: MongoException) { println("EXCEPTION: $exception") } return null } - private fun getCollection(name: String): MongoCollection? { - val client = getClient() ?: return null - val database = client.getDatabase(DATABASE_NAME) - return database.getCollection(name) + private fun getLogsCollection(id: String): MongoCollection? { + return getClient() + ?.getDatabase(DATABASE_NAME) + ?.getCollection(id + LOGS_COLLECTION_SUFFIX) + } + + // Configuration + + private fun getConfiguration(id: String): LogStorageConfiguration? { + return getClient() + ?.getDatabase(DATABASE_NAME) + ?.getCollection(id + CONFIG_COLLECTION_SUFFIX) + ?.find(eq("_id", LogStorageConfiguration.CONFIG_ID)) + ?.first() + ?.let { LogStorageConfiguration.from(it) } + } + + private fun saveConfiguration(id: String, configuration: LogStorageConfiguration) { + val options = ReplaceOptions().upsert(true) + getClient() + ?.getDatabase(DATABASE_NAME) + ?.getCollection(id + CONFIG_COLLECTION_SUFFIX) + ?.replaceOne( + eq("_id", LogStorageConfiguration.CONFIG_ID), + configuration.toDocument(), + options + ) + } + + // MongoDB - logs + + private suspend fun removeAllLogs(id: String) { + getLogsCollection(id)?.deleteMany(Document()) + } + + private suspend fun insertLogs(id: String, logs: List) { + getLogsCollection(id)?.insertMany(logs) + // TODO: Check for the limit of the number of lines + } + + private suspend fun downloadInsertLogs(id: String, seq: Long, skipFirstLines: Int? = null): Int { + var sequence = seq + val logs = connectionNew + .getLogs(skipFirstLines) + .mapNotNull { parser.parseLine(it) } + logs.forEach { + it.seq = sequence + sequence += 1 + } + + insertLogs(id, logs.map { it.toDocument() }) + return logs.size + } + + fun updateLogs(address: String, config: JsonObject) { + val id = config.getString(Props.ID) ?: return + + connectionNew.configuration = config + + // Get the last read line from the configuration + val storageConfig = getConfiguration(id) + var lastLine = storageConfig?.lastLine ?: 0 + var seq = storageConfig?.seq ?: 0 + println("LASTLINE: $lastLine") + + // Get the length of the file + coroutineScope.launch { + println("COROUTINE START") + + // Get the number of lines in the file + val fileLineCount = connectionNew.getNumberOfLines() ?: 0 + println("COROUTINE END, LINES: $fileLineCount") + + if (fileLineCount > 0 && fileLineCount > lastLine) { + // Download new logs and append + val inserted = downloadInsertLogs(id, seq, lastLine) + lastLine += inserted + seq += inserted + } else if (fileLineCount in 1 until lastLine) { + // Remove all logs and download from the beginning + removeAllLogs(id) + seq = 0 + val inserted = downloadInsertLogs(id, seq) + lastLine = inserted + seq += inserted.toLong() + } + // else do nothing + + saveConfiguration(id, LogStorageConfiguration(lastLine, seq)) + } + } + + // Temporary: SSH configuration + + private fun JsonObject.endpointProp(prop: String): String { + return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" + } + + private fun prepareConfig(config: JsonObject): JsonObject { + val tmpConfig = prepareConfigLines(config, + Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE + ) + + tmpConfig.getString(Props.AUTHENTICATION_TYPES) + ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(setOf(AuthenticationType.BASIC))) + return tmpConfig + } + + private fun prepareConfigLines(config: JsonObject, vararg fields: String): JsonObject { + for (field in fields) { + config.getString(field) ?: config.put(field, config.endpointProp(field)) + } + return config } companion object { private var client: MongoClient? = null private const val DATABASE_NAME: String = "logs" + private const val LOGS_COLLECTION_SUFFIX: String = "_logs" + private const val CONFIG_COLLECTION_SUFFIX: String = "_config" + + val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) } -} \ No newline at end of file +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 695a1523e..4c414269c 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import java.io.InputStream +import java.nio.charset.Charset class SSHClient : AbstractVerticle() { private lateinit var session: Session @@ -50,6 +51,7 @@ class SSHClient : AbstractVerticle() { coroutineScope.launch { try { connect(config) + executeCommandAndSendResult(config) } catch (e: JSchException) { LOGGER.error(e.message) val eventBusAddress = config.getString(CogboardConstants.Props.EVENT_ADDRESS) @@ -61,7 +63,6 @@ class SSHClient : AbstractVerticle() { private suspend fun connect(config: JsonObject) { val authData = SSHAuthData(config) createSSHChannel(authData) - executeCommandAndSendResult(config) } private suspend fun createSSHChannel(authData: SSHAuthData) { @@ -117,3 +118,72 @@ class SSHClient : AbstractVerticle() { val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) } } + +class SSHCoroutineClient(private val config: JsonObject) { + private var session: Session? = null + private var jsch: JSch? = null + + private suspend fun openSession() { + LOGGER.info(config) + val authData = SSHAuthData(config) + val jsch = JSch() + // jsch.setKnownHosts("~/.ssh/known_hosts") for security reasons this should be used + val session = SessionStrategyFactory(jsch).create(authData).initSession() + session.setConfig("StrictHostKeyChecking", "no") // not secure + session.connect(CogboardConstants.Props.SSH_TIMEOUT) + this.session = session + this.jsch = jsch + } + + fun closeSession() { + session?.disconnect() + session = null + jsch = null + } + + suspend fun execute(command: String): String? { + if (session == null || jsch == null) { + openSession() + } + if (session?.isConnected != true) { + return null + } + val (channel, inputStream) = createChannel(command) ?: return null + val response = readResponse(inputStream) + channel.disconnect() + return response + } + + suspend fun executeAndClose(command: String): String? { + val result = execute(command) + closeSession() + return result + } + + private suspend fun createChannel(command: String): Pair? { + val session = session ?: return null + val channel = session.openChannel("exec") as ChannelExec + channel.setCommand(command) + channel.inputStream = null + val inputStream = channel.inputStream + channel.connect(CogboardConstants.Props.SSH_TIMEOUT) + return Pair(channel, inputStream) + } + + private fun readResponse(stream: InputStream): String? { + val responseBuffer = Buffer.buffer() + val tmpBuf = ByteArray(512) + var readBytes = stream.read(tmpBuf, 0, 512) + while (readBytes != -1) { + responseBuffer.appendBytes(tmpBuf, 0, readBytes) + readBytes = stream.read(tmpBuf, 0, 512) + } + return responseBuffer.toString(Charset.defaultCharset()) + } + + companion object { + val LOGGER: Logger = LoggerFactory.getLogger(SSHCoroutineClient::class.java) + + val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index dcafef062..e99c467a4 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -21,3 +21,16 @@ abstract class ConnectionStrategy(protected val vertx: Vertx, protected val even abstract fun handleResponse(response: Any): String } + +abstract class ConnectionStrategyInt { + protected fun JsonObject.endpointProp(prop: String): String { + return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" + } + + protected open fun authenticationTypes(): Set { + return setOf(AuthenticationType.BASIC) + } + + abstract suspend fun getNumberOfLines(): Int? + abstract suspend fun getLogs(skipFirstLines: Int?): Collection +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index bd1805f9d..f010400a3 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -2,6 +2,7 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.ssh.SSHCoroutineClient import io.vertx.core.Vertx import io.vertx.core.buffer.Buffer import io.vertx.core.eventbus.MessageConsumer @@ -23,11 +24,12 @@ open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : (response as Buffer).toString(Charset.defaultCharset()) private fun prepareConfig(config: JsonObject): JsonObject { - val tmpConfig = prepareConfigLines(config = config, + val tmpConfig = prepareConfigLines(config, Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE ) - tmpConfig.getString(Props.AUTHENTICATION_TYPES) ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + tmpConfig.getString(Props.AUTHENTICATION_TYPES) + ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) tmpConfig.put(Props.EVENT_ADDRESS, eventBusAddress) return tmpConfig } @@ -39,3 +41,47 @@ open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : return config } } + +class SSHConnectionStrategyInt : ConnectionStrategyInt() { + + var configuration: JsonObject? = null + + override suspend fun getNumberOfLines(): Int? { + val config = configuration ?: return null + val logFilePath = config.getString(Props.PATH) ?: return null + + return SSHCoroutineClient(prepareConfig(config)) + .executeAndClose("wc -l < $logFilePath") + ?.trim() + ?.toIntOrNull() + } + + override suspend fun getLogs(skipFirstLines: Int?): Collection { + val config = configuration ?: return emptyList() + val logFilePath = config.getString(Props.PATH) ?: return emptyList() + val command = skipFirstLines?.let { "tail -n +${it + 1} $logFilePath" } ?: "cat $logFilePath" + + return SSHCoroutineClient(prepareConfig(config)) + .executeAndClose(command) + ?.trim() + ?.lines() + ?: emptyList() + } + + private fun prepareConfig(config: JsonObject): JsonObject { + val tmpConfig = prepareConfigLines(config, + Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE + ) + + tmpConfig.getString(Props.AUTHENTICATION_TYPES) + ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) + return tmpConfig + } + + private fun prepareConfigLines(config: JsonObject, vararg fields: String): JsonObject { + for (field in fields) { + config.getString(field) ?: config.put(field, config.endpointProp(field)) + } + return config + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index aa0c15adb..e14680997 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -2,12 +2,14 @@ package com.cognifide.cogboard.widget.type.logviewer import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService +import com.cognifide.cogboard.logStorage.MongoLogStorage import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategy import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategyFactory +import com.cognifide.cogboard.widget.type.logviewer.logparser.MockLogParser import io.vertx.core.Vertx import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer @@ -22,6 +24,7 @@ class LogViewerWidget( private var consumer: MessageConsumer<*>? = null private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() private val logParsingStrategy: LogParserStrategy = determineLogParsingStrategy() + private val logStorage: MongoLogStorage = MongoLogStorage(connectionStrategy, MockLogParser()) override fun start(): Widget { consumer = connectionStrategy.getConsumer(eventBusAddress) @@ -39,6 +42,7 @@ class LogViewerWidget( override fun updateState() { if (address.isNotBlank()) { connectionStrategy.sendRequest(address, config) + logStorage.updateLogs(address, config) } else { sendConfigurationError("Endpoint URL is blank") } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt index 09bc9b0eb..162042916 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt @@ -8,4 +8,4 @@ interface LogParser { fun parseLines(lines: Collection): List { return lines.mapNotNull { parseLine(it) } } -} \ No newline at end of file +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt index 2f3a2f532..72bf98823 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt @@ -6,7 +6,7 @@ import java.sql.Timestamp import java.time.LocalDateTime import java.time.format.DateTimeFormatter -class MockLogParser: LogParser { +class MockLogParser : LogParser { private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""" .trimMargin() .toRegex() @@ -27,8 +27,7 @@ class MockLogParser: LogParser { LogVariableData("Message", message, "No message description") ) - - return Log(date, type, variableData, null) + return Log(date = date, type = type, variableData = variableData) } companion object { @@ -38,4 +37,4 @@ class MockLogParser: LogParser { private const val PROVIDER = "provider" private const val MESSAGE = "message" } -} \ No newline at end of file +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt index 4b0d4dd0d..ab80261eb 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt @@ -7,7 +7,9 @@ import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companio import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.MESSAGE class MockLogParserStrategy : LogParserStrategy() { - private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""".trimMargin().toRegex() + private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""" + .trimMargin() + .toRegex() override fun parseLine(logLine: String): JsonObject { val groups = regex.matchEntire(logLine.trim())?.groups From 61e787e71581f107cd1a60efadd438f0d97607e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Thu, 18 Nov 2021 21:55:24 +0100 Subject: [PATCH 061/226] Change LogParser --- .../com/cognifide/cogboard/logStorage/Log.kt | 7 ++--- .../logStorage/LogStorageConfiguration.kt | 25 ++++++++++++----- .../cogboard/logStorage/MongoLogStorage.kt | 15 ++++++----- .../com/cognifide/cogboard/ssh/SSHClient.kt | 27 +++++++++---------- .../cogboard/ssh/auth/SSHAuthData.kt | 6 ++++- .../type/logviewer/logparser/LogParser.kt | 5 +--- .../type/logviewer/logparser/MockLogParser.kt | 18 ++++++++----- 7 files changed, 59 insertions(+), 44 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt index f90a30f1d..13e525718 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt @@ -5,13 +5,11 @@ import org.bson.types.ObjectId import io.vertx.core.json.JsonObject data class LogVariableData( - val name: String, val header: String, var description: String ) { private val map: Map get() = mapOf( - "name" to name, "header" to header, "description" to description) @@ -20,11 +18,10 @@ data class LogVariableData( companion object { fun from(document: Document): LogVariableData? { - val name = document.getString("name") ?: return null val header = document.getString("header") ?: return null val description = document.getString("description") ?: return null - return LogVariableData(name, header, description) + return LogVariableData(header, description) } } } @@ -54,7 +51,7 @@ data class Log( val date = document.getLong("date") val type = document.getString("type") - if(arrayOf(id, seq,date, type).contains(null)) { return null } + if (arrayOf(id, seq, date, type).contains(null)) { return null } val variableData = document .getList("variableData", Document::class.java) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt index 8f542e82f..5b9e4a592 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt @@ -2,21 +2,34 @@ package com.cognifide.cogboard.logStorage import org.bson.Document -data class LogStorageConfiguration(var lastLine: Int, var seq: Long) { +data class LogStorageConfiguration( + var id: String, + var lastLine: Int, + var seq: Long, + var variableFields: List +) { private val map: Map get() = mapOf( - "_id" to CONFIG_ID, + "_id" to id, "lastLine" to lastLine, - "seq" to seq + "seq" to seq, + "variableFields" to variableFields ) fun toDocument() = Document(map) companion object { const val CONFIG_ID: String = "config" fun from(document: Document): LogStorageConfiguration? { - val lastLine = document.getInteger("lastLine") ?: return null - val seq = document.getLong("seq") ?: return null - return LogStorageConfiguration(lastLine, seq) + val id = document.getString("_id") + val lastLine = document.getInteger("lastLine") + val seq = document.getLong("seq") + val variableFields = document.getList("variableFields", String::class.java) + + if (arrayOf(id, lastLine, seq, variableFields).contains(null)) { + return null + } + + return LogStorageConfiguration(id, lastLine, seq, emptyList()) } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt index e25a6ce0b..bcb419ea1 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt @@ -48,8 +48,8 @@ class MongoLogStorage(private val connection: ConnectionStrategy, private val pa private fun getConfiguration(id: String): LogStorageConfiguration? { return getClient() ?.getDatabase(DATABASE_NAME) - ?.getCollection(id + CONFIG_COLLECTION_SUFFIX) - ?.find(eq("_id", LogStorageConfiguration.CONFIG_ID)) + ?.getCollection(CONFIGURATION_COLLECTION_NAME) + ?.find(eq("_id", id)) ?.first() ?.let { LogStorageConfiguration.from(it) } } @@ -58,9 +58,9 @@ class MongoLogStorage(private val connection: ConnectionStrategy, private val pa val options = ReplaceOptions().upsert(true) getClient() ?.getDatabase(DATABASE_NAME) - ?.getCollection(id + CONFIG_COLLECTION_SUFFIX) + ?.getCollection(CONFIGURATION_COLLECTION_NAME) ?.replaceOne( - eq("_id", LogStorageConfiguration.CONFIG_ID), + eq("_id", id), configuration.toDocument(), options ) @@ -68,11 +68,11 @@ class MongoLogStorage(private val connection: ConnectionStrategy, private val pa // MongoDB - logs - private suspend fun removeAllLogs(id: String) { + private fun removeAllLogs(id: String) { getLogsCollection(id)?.deleteMany(Document()) } - private suspend fun insertLogs(id: String, logs: List) { + private fun insertLogs(id: String, logs: List) { getLogsCollection(id)?.insertMany(logs) // TODO: Check for the limit of the number of lines } @@ -125,7 +125,7 @@ class MongoLogStorage(private val connection: ConnectionStrategy, private val pa } // else do nothing - saveConfiguration(id, LogStorageConfiguration(lastLine, seq)) + saveConfiguration(id, LogStorageConfiguration(id, lastLine, seq, parser.variableFields)) } } @@ -155,6 +155,7 @@ class MongoLogStorage(private val connection: ConnectionStrategy, private val pa companion object { private var client: MongoClient? = null private const val DATABASE_NAME: String = "logs" + private const val CONFIGURATION_COLLECTION_NAME: String = "config" private const val LOGS_COLLECTION_SUFFIX: String = "_logs" private const val CONFIG_COLLECTION_SUFFIX: String = "_config" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 4c414269c..926a48c8c 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -102,11 +102,11 @@ class SSHClient : AbstractVerticle() { private fun readResponse(): Buffer { val responseBuffer = Buffer.buffer() - val tmpBuf = ByteArray(512) - var readBytes = sshInputStream.read(tmpBuf, 0, 512) + val tmpBuf = ByteArray(BUFFER_SIZE) + var readBytes = sshInputStream.read(tmpBuf, 0, BUFFER_SIZE) while (readBytes != -1) { responseBuffer.appendBytes(tmpBuf, 0, readBytes) - readBytes = sshInputStream.read(tmpBuf, 0, 512) + readBytes = sshInputStream.read(tmpBuf, 0, BUFFER_SIZE) } return responseBuffer @@ -114,7 +114,7 @@ class SSHClient : AbstractVerticle() { companion object { val LOGGER: Logger = LoggerFactory.getLogger(SSHClient::class.java) - + private const val BUFFER_SIZE: Int = 512 val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) } } @@ -123,7 +123,7 @@ class SSHCoroutineClient(private val config: JsonObject) { private var session: Session? = null private var jsch: JSch? = null - private suspend fun openSession() { + private fun openSession() { LOGGER.info(config) val authData = SSHAuthData(config) val jsch = JSch() @@ -141,7 +141,7 @@ class SSHCoroutineClient(private val config: JsonObject) { jsch = null } - suspend fun execute(command: String): String? { + fun execute(command: String): String? { if (session == null || jsch == null) { openSession() } @@ -154,13 +154,13 @@ class SSHCoroutineClient(private val config: JsonObject) { return response } - suspend fun executeAndClose(command: String): String? { + fun executeAndClose(command: String): String? { val result = execute(command) closeSession() return result } - private suspend fun createChannel(command: String): Pair? { + private fun createChannel(command: String): Pair? { val session = session ?: return null val channel = session.openChannel("exec") as ChannelExec channel.setCommand(command) @@ -172,18 +172,17 @@ class SSHCoroutineClient(private val config: JsonObject) { private fun readResponse(stream: InputStream): String? { val responseBuffer = Buffer.buffer() - val tmpBuf = ByteArray(512) - var readBytes = stream.read(tmpBuf, 0, 512) + val tmpBuf = ByteArray(BUFFER_SIZE) + var readBytes = stream.read(tmpBuf, 0, BUFFER_SIZE) while (readBytes != -1) { responseBuffer.appendBytes(tmpBuf, 0, readBytes) - readBytes = stream.read(tmpBuf, 0, 512) + readBytes = stream.read(tmpBuf, 0, BUFFER_SIZE) } return responseBuffer.toString(Charset.defaultCharset()) } companion object { - val LOGGER: Logger = LoggerFactory.getLogger(SSHCoroutineClient::class.java) - - val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) + private val LOGGER: Logger = LoggerFactory.getLogger(SSHCoroutineClient::class.java) + private const val BUFFER_SIZE: Int = 512 } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index 26ef790f3..0203c2916 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -13,7 +13,7 @@ class SSHAuthData(private val config: JsonObject) { val token = config.getString(CogboardConstants.Props.TOKEN) ?: "" val key = config.getString(CogboardConstants.Props.SSH_KEY) ?: "" val host = config.getString(CogboardConstants.Props.SSH_HOST) ?: "" - val port = config.getInteger(CogboardConstants.Props.SSH_PORT) ?: 22 + val port = config.getInteger(CogboardConstants.Props.SSH_PORT) ?: DEFAULT_PORT val authenticationType = fromConfigAuthenticationType() private fun fromConfigAuthenticationType(): AuthenticationType { @@ -44,4 +44,8 @@ class SSHAuthData(private val config: JsonObject) { return "cat $logFilePath | tail -$logLines" } + + companion object { + private const val DEFAULT_PORT: Int = 22 + } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt index 162042916..32d68c9b4 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParser.kt @@ -3,9 +3,6 @@ package com.cognifide.cogboard.widget.type.logviewer.logparser import main.kotlin.com.cognifide.cogboard.logStorage.Log interface LogParser { + val variableFields: List fun parseLine(line: String): Log? - - fun parseLines(lines: Collection): List { - return lines.mapNotNull { parseLine(it) } - } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt index 72bf98823..2af987b64 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt @@ -7,8 +7,9 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter class MockLogParser : LogParser { + override val variableFields = listOf("Provider", "Message") + private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""" - .trimMargin() .toRegex() override fun parseLine(line: String): Log? { @@ -17,14 +18,17 @@ class MockLogParser : LogParser { val date = groups[DATE]?.value ?.let { LocalDateTime.parse(it, dateTimeFormatter) } ?.let { Timestamp.valueOf(it)?.time } - ?: return null - val type = groups[TYPE]?.value ?: return null - val provider = groups[PROVIDER]?.value ?: return null - val message = groups[MESSAGE]?.value ?: return null + val type = groups[TYPE]?.value + val provider = groups[PROVIDER]?.value + val message = groups[MESSAGE]?.value + + if (date == null || type == null || provider == null || message == null) { + return null + } val variableData = listOf( - LogVariableData("Provider", provider, "No description"), - LogVariableData("Message", message, "No message description") + LogVariableData(provider, "No description"), + LogVariableData(message, "No message description") ) return Log(date = date, type = type, variableData = variableData) From 9db4fe437b832bc18e15784c833c1a8a8ece746c Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 19 Nov 2021 13:42:47 +0100 Subject: [PATCH 062/226] Add localStorage #389 --- .../components/widgets/dialogFields/index.js | 2 +- .../types/LogViewerWidget/LogList/index.js | 5 +++-- .../AdvancedFiltersMenu/helpers.js | 7 +++++++ .../FilterPicker/AdvancedFiltersMenu/index.js | 11 ++++++----- .../Toolbar/FilterPicker/index.js | 4 ++-- .../types/LogViewerWidget/Toolbar/index.js | 4 ++-- .../widgets/types/LogViewerWidget/index.js | 18 ++++++++++++------ cogboard-webapp/src/hooks/index.js | 13 +++++++++++++ 8 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/helpers.js diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index aeed0c34f..760448406 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -636,7 +636,7 @@ const dialogFields = { component: MultilineTextInput, name: 'regExp', label: 'Regular expression', - validator: () => string() + validator: () => string().required(vm.FIELD_REQUIRED()) } }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 4be204ffa..df43c2385 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -11,8 +11,9 @@ import { } from './styled'; import getGridTemplate from './helpers'; -export default function LogList({ logs, template, regExpFilters }) { +export default function LogList({ widgetLocalStorage, logs, template }) { const theme = useTheme(); + const filters = widgetLocalStorage?.regExpFilters || []; const filterByRegExp = (log, filters) => { let result = true; @@ -37,7 +38,7 @@ export default function LogList({ logs, template, regExpFilters }) { return result; }; - const filteredLogs = logs?.filter(log => filterByRegExp(log, regExpFilters)); + const filteredLogs = logs?.filter(log => filterByRegExp(log, filters)); const VariableLogListHeader = () => ( diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/helpers.js new file mode 100644 index 000000000..57cdf52a6 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/helpers.js @@ -0,0 +1,7 @@ +export const setFilters = ( + { get: localStorage, set: setLocalStorage }, + filters +) => { + const newWidgetData = { ...localStorage(), regExpFilters: filters }; + setLocalStorage(newWidgetData); +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index c14f80bc5..03388aa74 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; - +import React from 'react'; import { useToggle } from '../../../../../../../hooks'; +import { setFilters } from './helpers'; import { Button, @@ -15,17 +15,18 @@ import EditFilter from './EditFilter'; import DeleteItem from '../../../../../../DeleteItem'; import FilterForm from './FilterForm'; -const AdvancedFiltersMenu = ({ regExpFilters }) => { +const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); + const filters = widgetLocalStorage.get()?.regExpFilters || []; - const [filters, setFilters] = regExpFilters; const addFilter = values => { const maxId = filters.reduce((acc, { id }) => (id > acc ? id : acc), 0); - setFilters([...filters, { id: maxId + 1, ...values }]); + setFilters(widgetLocalStorage, [...filters, { id: maxId + 1, ...values }]); }; const editFilter = ({ id, values }) => { setFilters( + widgetLocalStorage, filters.map(filter => { if (filter.id === id) { return { id, ...values }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index c5f14dbbb..d897c1bca 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -14,7 +14,7 @@ import { ScrollableBox } from './styled'; import ToolbarGroup from '../ToolbarGroup'; import AdvancedFiltersMenu from './AdvancedFiltersMenu'; -const FilterPicker = ({ regExpFilters }) => { +const FilterPicker = ({ widgetLocalStorage }) => { const handleDelete = name => { setFilters(filters.filter(item => item !== name)); }; @@ -75,7 +75,7 @@ const FilterPicker = ({ regExpFilters }) => { ))} - + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 11b1dd5a0..fd9443802 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -8,7 +8,7 @@ import GetAppIcon from '@material-ui/icons/GetApp'; import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; -const Toolbar = ({ regExpFilters }) => { +const Toolbar = ({ widgetLocalStorage }) => { const theme = useTheme(); return ( @@ -17,7 +17,7 @@ const Toolbar = ({ regExpFilters }) => { - + diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 3a767b123..9258d7817 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,29 +1,35 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import { number, string } from 'prop-types'; +import { useLocalStorage } from '../../../../hooks'; + import Toolbar from './Toolbar'; import LogList from './LogList'; import { Container } from './styled'; const LogViewerWidget = ({ id }) => { - const [regExpFiltersGet, regExpFiltersSet] = useState([]); - const regExpFilters = [regExpFiltersGet, regExpFiltersSet]; - const widgetData = useSelector( ({ widgets }) => widgets.widgetsById[id], shallowEqual ); useEffect(() => console.log(widgetData), [widgetData]); + const [widgetLocalStorage, setWidgetLocalStorage] = useLocalStorage(id); + const logs = widgetData.content?.logs; return ( - + widgetLocalStorage, + set: setWidgetLocalStorage + }} + /> {logs && ( )} diff --git a/cogboard-webapp/src/hooks/index.js b/cogboard-webapp/src/hooks/index.js index cb3dd5f15..a03e834a6 100644 --- a/cogboard-webapp/src/hooks/index.js +++ b/cogboard-webapp/src/hooks/index.js @@ -136,3 +136,16 @@ export function useEventListener(eventName, handler, element = window) { }; }, [eventName, element]); } + +export function useLocalStorage(key) { + const localStorage = window.localStorage.getItem(key); + const [data, setStoredValue] = useState( + localStorage ? JSON.parse(localStorage) : null + ); + + const setData = data => { + window.localStorage.setItem(key, JSON.stringify(data)); + setStoredValue(data); + }; + return [data, setData]; +} From 76dc87cb87f30c36a9f35f5c89b93a35b030530f Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 19 Nov 2021 14:23:03 +0100 Subject: [PATCH 063/226] Add delete action #389 --- cogboard-webapp/src/components/DeleteItem.js | 4 +--- .../src/components/SettingsMenu/index.js | 2 +- .../FilterPicker/AdvancedFiltersMenu/index.js | 14 ++++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cogboard-webapp/src/components/DeleteItem.js b/cogboard-webapp/src/components/DeleteItem.js index 4cd91ccab..30024619f 100644 --- a/cogboard-webapp/src/components/DeleteItem.js +++ b/cogboard-webapp/src/components/DeleteItem.js @@ -1,5 +1,4 @@ import React from 'react'; -import { useDispatch } from 'react-redux'; import { useToggle } from '../hooks'; @@ -8,11 +7,10 @@ import { Delete } from '@material-ui/icons'; import ConfirmationDialog from './ConfirmationDialog'; const DeleteItem = ({ id, label, itemName, deleteAction }) => { - const dispatch = useDispatch(); const [dialogOpened, openDialog, handleDialogClose] = useToggle(); const handleDelete = id => () => { - dispatch(deleteAction(id)); + deleteAction(id); handleDialogClose(); }; diff --git a/cogboard-webapp/src/components/SettingsMenu/index.js b/cogboard-webapp/src/components/SettingsMenu/index.js index 0752f24e3..293a5a040 100644 --- a/cogboard-webapp/src/components/SettingsMenu/index.js +++ b/cogboard-webapp/src/components/SettingsMenu/index.js @@ -84,7 +84,7 @@ const SettingsMenu = ({ className }) => { id={id} label={label} itemName={name} - deleteAction={deleteAction} + deleteAction={id => dispatch(deleteAction(id))} /> diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 03388aa74..f4db876a6 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -36,6 +36,13 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { ); }; + const deleteFilter = id => { + setFilters( + widgetLocalStorage, + filters.filter(filter => filter.id !== id) + ); + }; + const renderListItems = ( items, name, @@ -48,13 +55,12 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { - {/* USES REDUX */} - {/* */} + /> )); @@ -76,7 +82,7 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { 'filter', EditFilter, editFilter, - () => 'D' + deleteFilter )} From 573e44376ad722cdc1849c8025d216c8255c3379 Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 19 Nov 2021 16:04:31 +0100 Subject: [PATCH 064/226] Add multiselect support #389 --- .../types/LogViewerWidget/LogList/index.js | 30 ++++++----- .../FilterPicker/AdvancedFiltersMenu/index.js | 25 +++++++-- .../{AdvancedFiltersMenu => }/helpers.js | 0 .../Toolbar/FilterPicker/index.js | 51 +++++++++++-------- 4 files changed, 68 insertions(+), 38 deletions(-) rename cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/{AdvancedFiltersMenu => }/helpers.js (100%) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index df43c2385..ca2b442b0 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -17,22 +17,24 @@ export default function LogList({ widgetLocalStorage, logs, template }) { const filterByRegExp = (log, filters) => { let result = true; - filters.forEach(({ regExp }) => { - let filterPassed = false; - const regExpObj = new RegExp(regExp); - const texts = [ - ...log.variableData.headers, - ...log.variableData.description - ]; + filters.forEach(({ regExp, checked }) => { + if (checked) { + let filterPassed = false; + const regExpObj = new RegExp(regExp); + const texts = [ + ...log.variableData.headers, + ...log.variableData.description + ]; - texts.forEach(text => { - if (text.match(regExpObj)) { - filterPassed = true; - } - }); + texts.forEach(text => { + if (text.match(regExpObj)) { + filterPassed = true; + } + }); - if (!filterPassed) { - result = false; + if (!filterPassed) { + result = false; + } } }); return result; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index f4db876a6..abb85dd80 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -1,13 +1,14 @@ import React from 'react'; import { useToggle } from '../../../../../../../hooks'; -import { setFilters } from './helpers'; +import { setFilters } from '../helpers'; import { Button, List, ListItem, ListItemText, - ListItemSecondaryAction + ListItemSecondaryAction, + Switch } from '@material-ui/core'; import AppDialog from '../../../../../../AppDialog'; import AddItem from '../../../../../../AddItem'; @@ -21,7 +22,10 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { const addFilter = values => { const maxId = filters.reduce((acc, { id }) => (id > acc ? id : acc), 0); - setFilters(widgetLocalStorage, [...filters, { id: maxId + 1, ...values }]); + setFilters(widgetLocalStorage, [ + ...filters, + { id: maxId + 1, checked: true, ...values } + ]); }; const editFilter = ({ id, values }) => { @@ -43,6 +47,14 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { ); }; + const handleSwitch = id => + setFilters( + widgetLocalStorage, + filters.map(filter => + filter.id === id ? { ...filter, checked: !filter.checked } : filter + ) + ); + const renderListItems = ( items, name, @@ -50,7 +62,7 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { editAction, deleteAction ) => - items.map(({ id, label }) => ( + items.map(({ id, label, checked }) => ( @@ -61,6 +73,11 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { itemName={name} deleteAction={deleteAction} /> + handleSwitch(id)} + color="secondary" + /> )); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js similarity index 100% rename from cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/helpers.js rename to cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index d897c1bca..2e7f77020 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -1,9 +1,9 @@ -import React from 'react'; -import { useState } from 'react'; +import React, { useState } from 'react'; +import { setFilters } from './helpers'; + import logLevels from '../../logLevels'; import { - Button, Select, Chip, MenuItem, @@ -15,13 +15,26 @@ import ToolbarGroup from '../ToolbarGroup'; import AdvancedFiltersMenu from './AdvancedFiltersMenu'; const FilterPicker = ({ widgetLocalStorage }) => { - const handleDelete = name => { - setFilters(filters.filter(item => item !== name)); - }; - - const [filters, setFilters] = useState([]); + const regExpFilters = widgetLocalStorage.get()?.regExpFilters || []; const [logLevel, setLogLevel] = useState('info'); + const handleSelection = selectedList => + setFilters( + widgetLocalStorage, + regExpFilters.map(filter => ({ + ...filter, + checked: selectedList.map(({ id }) => id).includes(filter.id) + })) + ); + + const handleDelete = id => + setFilters( + widgetLocalStorage, + regExpFilters.map(filter => + filter.id === id ? { ...filter, checked: !filter.checked } : filter + ) + ); + return ( @@ -31,19 +44,17 @@ const FilterPicker = ({ widgetLocalStorage }) => { labelId="filters-label" multiple style={{ width: '200px' }} - value={filters} + value={regExpFilters.filter(filter => filter.checked)} size="small" - onChange={e => setFilters(e.target.value)} + onChange={e => handleSelection(e.target.value)} renderValue={selected => ( - {selected.map(value => ( + {selected.map(({ id, label }) => ( handleDelete(value)} - onMouseDown={e => { - e.stopPropagation(); - }} + key={id} + label={label} + onDelete={() => handleDelete(id)} + onMouseDown={e => e.stopPropagation()} style={{ marginRight: '4px' }} size="small" /> @@ -51,9 +62,9 @@ const FilterPicker = ({ widgetLocalStorage }) => { )} > - {logLevels.map((level, index) => ( - - {level.value.toUpperCase()} + {regExpFilters.map(filter => ( + + {filter.label} ))} From 5734098ceef453baa4a13dd35e910c936eb1791b Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 19 Nov 2021 16:18:11 +0100 Subject: [PATCH 065/226] Add implicit checked on edit #389 --- .../Toolbar/FilterPicker/AdvancedFiltersMenu/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index abb85dd80..a83be18e5 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -33,7 +33,7 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { widgetLocalStorage, filters.map(filter => { if (filter.id === id) { - return { id, ...values }; + return { id, checked: true, ...values }; } return filter; }) From 1259f82d29b55e814f7f0c395cbd7bb37aea9020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sat, 20 Nov 2021 01:56:28 +0100 Subject: [PATCH 066/226] Work on storage, disable logs --- .../cognifide/cogboard/CogboardConstants.kt | 2 + .../com/cognifide/cogboard/logStorage/Log.kt | 24 +-- .../logStorage/LogCollectionConfiguration.kt | 32 ++++ .../logStorage/LogStorageConfiguration.kt | 39 +---- .../cogboard/logStorage/MongoLogStorage.kt | 157 +++++++++--------- .../connectionStrategy/ConnectionStrategy.kt | 4 +- .../SSHConnectionStrategy.kt | 10 +- .../widget/type/logviewer/LogViewerWidget.kt | 29 +++- .../widget/type/logviewer/LogViewerTest.kt | 68 ++++---- 9 files changed, 195 insertions(+), 170 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index c7f607334..5984d27d1 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -47,6 +47,8 @@ class CogboardConstants { const val LOG_REQUEST_TYPE = "logRequestType" const val LOG_LINES = "logLinesField" + const val LOG_FILE_SIZE = "logFileSizeField" + const val LOG_EXPIRATION_DAYS = "logRecordExpirationField" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" const val USER = "user" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt index 13e525718..b9920ea55 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt @@ -33,16 +33,20 @@ data class Log( var type: String, var variableData: List ) { - private val map: Map - get() = mapOf( - "_id" to id, - "seq" to seq, - "date" to date, - "type" to type, - "variableData" to variableData.map { it.toDocument() } - ) - fun toDocument() = Document(map) - fun toJson() = JsonObject(map) + fun toDocument() = Document(mapOf( + "_id" to id, + "seq" to seq, + "date" to date, + "type" to type, + "variableData" to variableData.map { it.toDocument() } + )) + fun toJson() = JsonObject(mapOf( + "_id" to id.toHexString(), + "seq" to seq, + "date" to date, + "type" to type, + "variableData" to variableData.map { it.toJson() } + )) companion object { fun from(document: Document): Log? { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt new file mode 100644 index 000000000..82ef230aa --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt @@ -0,0 +1,32 @@ +package com.cognifide.cogboard.logStorage + +import org.bson.Document + +data class LogCollectionConfiguration( + var id: String, + var lastLine: Int, + var seq: Long +) { + private val map: Map + get() = mapOf( + "_id" to id, + "lastLine" to lastLine, + "seq" to seq + ) + fun toDocument() = Document(map) + + companion object { + const val CONFIG_ID: String = "config" + fun from(document: Document): LogCollectionConfiguration? { + val id = document.getString("_id") + val lastLine = document.getInteger("lastLine") + val seq = document.getLong("seq") + + if (arrayOf(id, lastLine, seq).contains(null)) { + return null + } + + return LogCollectionConfiguration(id, lastLine, seq) + } + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt index 5b9e4a592..816caad8d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt @@ -1,35 +1,8 @@ -package com.cognifide.cogboard.logStorage - -import org.bson.Document +package main.kotlin.com.cognifide.cogboard.logStorage data class LogStorageConfiguration( - var id: String, - var lastLine: Int, - var seq: Long, - var variableFields: List -) { - private val map: Map - get() = mapOf( - "_id" to id, - "lastLine" to lastLine, - "seq" to seq, - "variableFields" to variableFields - ) - fun toDocument() = Document(map) - - companion object { - const val CONFIG_ID: String = "config" - fun from(document: Document): LogStorageConfiguration? { - val id = document.getString("_id") - val lastLine = document.getInteger("lastLine") - val seq = document.getLong("seq") - val variableFields = document.getList("variableFields", String::class.java) - - if (arrayOf(id, lastLine, seq, variableFields).contains(null)) { - return null - } - - return LogStorageConfiguration(id, lastLine, seq, emptyList()) - } - } -} + val id: String, + val logLines: Int, + val fileSizeMB: Int, + val expirationDays: Int +) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt index bcb419ea1..5ced7d182 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt @@ -1,66 +1,50 @@ package com.cognifide.cogboard.logStorage -import com.cognifide.cogboard.CogboardConstants -import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.http.auth.AuthenticationType -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy -import com.cognifide.cogboard.widget.connectionStrategy.SSHConnectionStrategyInt +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyInt import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParser import com.mongodb.client.MongoClient import com.mongodb.MongoException import com.mongodb.client.MongoClients import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoDatabase import com.mongodb.client.model.Filters.eq +import com.mongodb.client.model.Sorts.descending import com.mongodb.client.model.ReplaceOptions import io.vertx.core.AbstractVerticle -import io.vertx.core.json.Json +import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject import org.bson.Document import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import main.kotlin.com.cognifide.cogboard.logStorage.Log +import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration -class MongoLogStorage(private val connection: ConnectionStrategy, private val parser: LogParser) : AbstractVerticle() { +class MongoLogStorage( + private val config: LogStorageConfiguration, + private val connection: ConnectionStrategyInt, + private val parser: LogParser +) : AbstractVerticle() { - private var connectionNew = SSHConnectionStrategyInt() - private fun getClient(): MongoClient? { - if (client != null) { - return client - } - try { - client = MongoClients.create("mongodb://root:root@mongo:27017/") - return client - } catch (exception: MongoException) { - println("EXCEPTION: $exception") - } - return null - } + private val logsCollection: MongoCollection? + get() = database?.getCollection(config.id + LOGS_COLLECTION_SUFFIX) - private fun getLogsCollection(id: String): MongoCollection? { - return getClient() - ?.getDatabase(DATABASE_NAME) - ?.getCollection(id + LOGS_COLLECTION_SUFFIX) - } + // Storage configuration - // Configuration - - private fun getConfiguration(id: String): LogStorageConfiguration? { - return getClient() - ?.getDatabase(DATABASE_NAME) - ?.getCollection(CONFIGURATION_COLLECTION_NAME) - ?.find(eq("_id", id)) - ?.first() - ?.let { LogStorageConfiguration.from(it) } - } + private val collectionConfiguration: LogCollectionConfiguration? + get() = database + ?.getCollection(CONFIGURATION_COLLECTION_NAME) + ?.find(eq("_id", config.id)) + ?.first() + ?.let { LogCollectionConfiguration.from(it) } - private fun saveConfiguration(id: String, configuration: LogStorageConfiguration) { + private fun saveConfiguration(configuration: LogCollectionConfiguration) { val options = ReplaceOptions().upsert(true) - getClient() - ?.getDatabase(DATABASE_NAME) + database ?.getCollection(CONFIGURATION_COLLECTION_NAME) ?.replaceOne( - eq("_id", id), + eq("_id", config.id), configuration.toDocument(), options ) @@ -68,18 +52,13 @@ class MongoLogStorage(private val connection: ConnectionStrategy, private val pa // MongoDB - logs - private fun removeAllLogs(id: String) { - getLogsCollection(id)?.deleteMany(Document()) + private fun removeAllLogs() { + logsCollection?.deleteMany(Document()) } - private fun insertLogs(id: String, logs: List) { - getLogsCollection(id)?.insertMany(logs) - // TODO: Check for the limit of the number of lines - } - - private suspend fun downloadInsertLogs(id: String, seq: Long, skipFirstLines: Int? = null): Int { + private fun downloadInsertLogs(seq: Long, skipFirstLines: Int? = null): Int { var sequence = seq - val logs = connectionNew + val logs = connection .getLogs(skipFirstLines) .mapNotNull { parser.parseLine(it) } logs.forEach { @@ -87,77 +66,91 @@ class MongoLogStorage(private val connection: ConnectionStrategy, private val pa sequence += 1 } - insertLogs(id, logs.map { it.toDocument() }) + logsCollection?.insertMany(logs.map { it.toDocument() }) + // TODO: Check for the limit of the number of lines + return logs.size } - fun updateLogs(address: String, config: JsonObject) { - val id = config.getString(Props.ID) ?: return + private val logs: List + get() = logsCollection + ?.find() + ?.sort(descending("seq")) + ?.mapNotNull { it } + ?.mapNotNull { Log.from(it) } + ?: emptyList() + + private fun prepareResponse(): JsonObject { + return JsonObject(mapOf( + "variableFields" to parser.variableFields, + "logs" to JsonArray(logs.map { it.toJson() }) + )) + } - connectionNew.configuration = config + fun updateLogs() { // Get the last read line from the configuration - val storageConfig = getConfiguration(id) + val storageConfig = collectionConfiguration var lastLine = storageConfig?.lastLine ?: 0 var seq = storageConfig?.seq ?: 0 println("LASTLINE: $lastLine") + println("HAVE VERTX A? $vertx") // Get the length of the file coroutineScope.launch { println("COROUTINE START") + println("HAVE VERTX B? $vertx") // Get the number of lines in the file - val fileLineCount = connectionNew.getNumberOfLines() ?: 0 + val fileLineCount = connection.getNumberOfLines() ?: 0 println("COROUTINE END, LINES: $fileLineCount") if (fileLineCount > 0 && fileLineCount > lastLine) { // Download new logs and append - val inserted = downloadInsertLogs(id, seq, lastLine) + val inserted = downloadInsertLogs(seq, lastLine) lastLine += inserted seq += inserted } else if (fileLineCount in 1 until lastLine) { // Remove all logs and download from the beginning - removeAllLogs(id) + removeAllLogs() seq = 0 - val inserted = downloadInsertLogs(id, seq) + val inserted = downloadInsertLogs(seq) lastLine = inserted seq += inserted.toLong() } // else do nothing - saveConfiguration(id, LogStorageConfiguration(id, lastLine, seq, parser.variableFields)) - } - } + saveConfiguration(LogCollectionConfiguration(config.id, lastLine, seq)) - // Temporary: SSH configuration - - private fun JsonObject.endpointProp(prop: String): String { - return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" - } - - private fun prepareConfig(config: JsonObject): JsonObject { - val tmpConfig = prepareConfigLines(config, - Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE - ) - - tmpConfig.getString(Props.AUTHENTICATION_TYPES) - ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(setOf(AuthenticationType.BASIC))) - return tmpConfig - } - - private fun prepareConfigLines(config: JsonObject, vararg fields: String): JsonObject { - for (field in fields) { - config.getString(field) ?: config.put(field, config.endpointProp(field)) + // Fetch the logs from database + val response = prepareResponse() + println("HAVE VERTX C? $vertx") + vertx?.eventBus()?.send("event.widget.widget1_alt", response) } - return config } companion object { - private var client: MongoClient? = null private const val DATABASE_NAME: String = "logs" private const val CONFIGURATION_COLLECTION_NAME: String = "config" private const val LOGS_COLLECTION_SUFFIX: String = "_logs" - private const val CONFIG_COLLECTION_SUFFIX: String = "_config" + private var mongoClient: MongoClient? = null + + private val client: MongoClient? + get() { + if (mongoClient != null) { + return mongoClient + } + try { + mongoClient = MongoClients.create("mongodb://root:root@mongo:27017/") + return mongoClient + } catch (exception: MongoException) { + println("EXCEPTION: $exception") + } + return null + } + + private val database: MongoDatabase? + get() = client?.getDatabase(DATABASE_NAME) val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index e99c467a4..5e40e4115 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -31,6 +31,6 @@ abstract class ConnectionStrategyInt { return setOf(AuthenticationType.BASIC) } - abstract suspend fun getNumberOfLines(): Int? - abstract suspend fun getLogs(skipFirstLines: Int?): Collection + abstract fun getNumberOfLines(): Int? + abstract fun getLogs(skipFirstLines: Int?): Collection } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index f010400a3..ad5f41a90 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -42,12 +42,9 @@ open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : } } -class SSHConnectionStrategyInt : ConnectionStrategyInt() { +class SSHConnectionStrategyInt(val config: JsonObject) : ConnectionStrategyInt() { - var configuration: JsonObject? = null - - override suspend fun getNumberOfLines(): Int? { - val config = configuration ?: return null + override fun getNumberOfLines(): Int? { val logFilePath = config.getString(Props.PATH) ?: return null return SSHCoroutineClient(prepareConfig(config)) @@ -56,8 +53,7 @@ class SSHConnectionStrategyInt : ConnectionStrategyInt() { ?.toIntOrNull() } - override suspend fun getLogs(skipFirstLines: Int?): Collection { - val config = configuration ?: return emptyList() + override fun getLogs(skipFirstLines: Int?): Collection { val logFilePath = config.getString(Props.PATH) ?: return emptyList() val command = skipFirstLines?.let { "tail -n +${it + 1} $logFilePath" } ?: "cat $logFilePath" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index e14680997..12cbe30a1 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -7,6 +7,8 @@ import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyInt +import com.cognifide.cogboard.widget.connectionStrategy.SSHConnectionStrategyInt import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategy import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategyFactory import com.cognifide.cogboard.widget.type.logviewer.logparser.MockLogParser @@ -14,6 +16,7 @@ import io.vertx.core.Vertx import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject +import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration class LogViewerWidget( vertx: Vertx, @@ -23,18 +26,31 @@ class LogViewerWidget( private val address = config.endpointProp(Props.URL) private var consumer: MessageConsumer<*>? = null private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() + private val connectionStrategyInt: ConnectionStrategyInt = SSHConnectionStrategyInt(config) private val logParsingStrategy: LogParserStrategy = determineLogParsingStrategy() - private val logStorage: MongoLogStorage = MongoLogStorage(connectionStrategy, MockLogParser()) + private val logStorage = MongoLogStorage( + buildConfiguration(config), + connectionStrategyInt, + MockLogParser() + ) override fun start(): Widget { + vertx.deployVerticle(logStorage) consumer = connectionStrategy.getConsumer(eventBusAddress) consumer!!.handler { handleResponse(it) } + vertx.eventBus().consumer(eventBusAddress + "_alt").handler { message -> + message?.body()?.let { + val s = it.toString() + " " + println("HANDLED $s") + } + } return super.start() } override fun stop(): Widget { + logStorage.deploymentID()?.let { vertx.undeploy(it) } consumer?.unregister() return super.stop() } @@ -42,7 +58,7 @@ class LogViewerWidget( override fun updateState() { if (address.isNotBlank()) { connectionStrategy.sendRequest(address, config) - logStorage.updateLogs(address, config) + logStorage.updateLogs() } else { sendConfigurationError("Endpoint URL is blank") } @@ -78,4 +94,13 @@ class LogViewerWidget( private fun determineLogParsingStrategy() = LogParserStrategyFactory() .build(LogParserStrategyFactory.MOCK) + + private fun buildConfiguration(config: JsonObject): LogStorageConfiguration { + return LogStorageConfiguration( + config.getString(Props.ID) ?: "0", + config.getInteger(Props.LOG_LINES) ?: 100, + config.getInteger(Props.LOG_FILE_SIZE) ?: 50, + config.getInteger(Props.LOG_EXPIRATION_DAYS) ?: 5 + ) + } } diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerTest.kt index a06401d31..3b838644a 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerTest.kt @@ -23,40 +23,40 @@ class LogViewerTest: WidgetTestBase() { super.init() } - @Test - fun `Expect Buffer consumer to be used when type is SSH`() { - val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer - `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) - - val endpoint = mockEndpointData("ssh") - - val config = initWidget() - .put(Props.ENDPOINT_LOADED, endpoint) - .put(Props.LOG_LINES, "5") - widget = LogViewerWidget(vertx, config, initService()) - - widget.start() - - verify(eventBus).consumer(eq(widget.eventBusAddress)) - } - - @Test - fun `Expect JsonObject consumer to be used when type is HTTP`() { - val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer - `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) - - val endpoint = mockEndpointData("http") - - val config = initWidget() - .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) - .put(Props.ENDPOINT_LOADED, endpoint) - .put(Props.LOG_LINES, "5") - widget = LogViewerWidget(vertx, config, initService()) - - widget.start() - - verify(eventBus).consumer(eq(widget.eventBusAddress)) - } +// @Test +// fun `Expect Buffer consumer to be used when type is SSH`() { +// val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer +// `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) +// +// val endpoint = mockEndpointData("ssh") +// +// val config = initWidget() +// .put(Props.ENDPOINT_LOADED, endpoint) +// .put(Props.LOG_LINES, "5") +// widget = LogViewerWidget(vertx, config, initService()) +// +// widget.start() +// +// verify(eventBus).consumer(eq(widget.eventBusAddress)) +// } +// +// @Test +// fun `Expect JsonObject consumer to be used when type is HTTP`() { +// val consumerMock = mock(MessageConsumer::class.java) as MessageConsumer +// `when`(eventBus.consumer(anyString())).thenReturn(consumerMock) +// +// val endpoint = mockEndpointData("http") +// +// val config = initWidget() +// .put(Props.LOG_REQUEST_TYPE, RequestMethod.GET) +// .put(Props.ENDPOINT_LOADED, endpoint) +// .put(Props.LOG_LINES, "5") +// widget = LogViewerWidget(vertx, config, initService()) +// +// widget.start() +// +// verify(eventBus).consumer(eq(widget.eventBusAddress)) +// } private fun mockEndpointData(protocol: String): JsonObject = JsonObject(mapOf(Pair(Props.URL, "$protocol://192.168.0.1"))) From 741347b8496019af6c44d0aafad2a1b23cd76685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sat, 20 Nov 2021 18:27:39 +0100 Subject: [PATCH 067/226] Connect the database with the frontend --- .../com/cognifide/cogboard/logStorage/Log.kt | 47 +++++--- .../logStorage/LogCollectionConfiguration.kt | 4 +- .../logStorage/LogStorageConfiguration.kt | 7 +- .../cogboard/logStorage/MongoLogStorage.kt | 102 ++++++++++++++---- .../com/cognifide/cogboard/ssh/SSHClient.kt | 6 +- .../connectionStrategy/ConnectionStrategy.kt | 4 +- .../SSHConnectionStrategy.kt | 6 +- .../widget/type/logviewer/LogViewerWidget.kt | 54 +++------- .../types/LogViewerWidget/LogList/LogEntry.js | 59 ++++------ .../types/LogViewerWidget/LogList/index.js | 3 +- .../types/LogViewerWidget/LogList/styled.js | 1 + .../widgets/types/LogViewerWidget/index.js | 4 +- 12 files changed, 165 insertions(+), 132 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt index b9920ea55..6c6a7c6ea 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt @@ -3,6 +3,8 @@ package main.kotlin.com.cognifide.cogboard.logStorage import org.bson.Document import org.bson.types.ObjectId import io.vertx.core.json.JsonObject +import java.time.Instant +import java.util.* data class LogVariableData( val header: String, @@ -29,39 +31,50 @@ data class LogVariableData( data class Log( var id: ObjectId = ObjectId(), var seq: Long = 0, + var insertedOn: Long = Instant.now().epochSecond, var date: Long, var type: String, var variableData: List ) { fun toDocument() = Document(mapOf( - "_id" to id, - "seq" to seq, - "date" to date, - "type" to type, - "variableData" to variableData.map { it.toDocument() } + ID to id, + SEQ to seq, + INSERTED_ON to insertedOn, + DATE to date, + TYPE to type, + VARIABLE_DATA to variableData.map { it.toDocument() } )) fun toJson() = JsonObject(mapOf( - "_id" to id.toHexString(), - "seq" to seq, - "date" to date, - "type" to type, - "variableData" to variableData.map { it.toJson() } + ID to id.toHexString(), + SEQ to seq, + INSERTED_ON to insertedOn, + DATE to date, + TYPE to type, + VARIABLE_DATA to variableData.map { it.toJson() } )) companion object { + const val ID = "_id" + const val SEQ = "seq" + const val INSERTED_ON = "insertedOn" + const val DATE = "date" + const val TYPE = "type" + const val VARIABLE_DATA = "variableData" + fun from(document: Document): Log? { - val id = document.getObjectId("_id") - val seq = document.getLong("seq") - val date = document.getLong("date") - val type = document.getString("type") + val id = document.getObjectId(ID) + val seq = document.getLong(SEQ) + val insertedOn = document.getLong(INSERTED_ON) + val date = document.getLong(DATE) + val type = document.getString(TYPE) - if (arrayOf(id, seq, date, type).contains(null)) { return null } + if (arrayOf(id, seq, insertedOn, date, type).contains(null)) { return null } val variableData = document - .getList("variableData", Document::class.java) + .getList(VARIABLE_DATA, Document::class.java) ?.mapNotNull { it } ?.mapNotNull { LogVariableData.from(it) } ?: listOf() - return Log(id, seq, date, type, variableData) + return Log(id, seq, insertedOn, date, type, variableData) } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt index 82ef230aa..fc32ee5fb 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt @@ -4,7 +4,7 @@ import org.bson.Document data class LogCollectionConfiguration( var id: String, - var lastLine: Int, + var lastLine: Long, var seq: Long ) { private val map: Map @@ -19,7 +19,7 @@ data class LogCollectionConfiguration( const val CONFIG_ID: String = "config" fun from(document: Document): LogCollectionConfiguration? { val id = document.getString("_id") - val lastLine = document.getInteger("lastLine") + val lastLine = document.getLong("lastLine") val seq = document.getLong("seq") if (arrayOf(id, lastLine, seq).contains(null)) { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt index 816caad8d..794b1a8de 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt @@ -2,7 +2,8 @@ package main.kotlin.com.cognifide.cogboard.logStorage data class LogStorageConfiguration( val id: String, - val logLines: Int, - val fileSizeMB: Int, - val expirationDays: Int + val logLines: Long, + val fileSizeMB: Long, + val expirationDays: Long, + val eventBusAddress: String ) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt index 5ced7d182..df25d9604 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt @@ -7,12 +7,14 @@ import com.mongodb.MongoException import com.mongodb.client.MongoClients import com.mongodb.client.MongoCollection import com.mongodb.client.MongoDatabase -import com.mongodb.client.model.Filters.eq +import com.mongodb.client.model.Filters.* import com.mongodb.client.model.Sorts.descending import com.mongodb.client.model.ReplaceOptions import io.vertx.core.AbstractVerticle import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject +import io.vertx.core.logging.Logger +import io.vertx.core.logging.LoggerFactory import org.bson.Document import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -20,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import main.kotlin.com.cognifide.cogboard.logStorage.Log import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration +import java.time.Instant class MongoLogStorage( private val config: LogStorageConfiguration, @@ -27,36 +30,93 @@ class MongoLogStorage( private val parser: LogParser ) : AbstractVerticle() { + /** Returns a logs collection associated with this widget. */ private val logsCollection: MongoCollection? get() = database?.getCollection(config.id + LOGS_COLLECTION_SUFFIX) // Storage configuration + /** Returns a logs collection configuration associated with this widget (if present). */ private val collectionConfiguration: LogCollectionConfiguration? get() = database ?.getCollection(CONFIGURATION_COLLECTION_NAME) - ?.find(eq("_id", config.id)) + ?.find(eq(Log.ID, config.id)) ?.first() ?.let { LogCollectionConfiguration.from(it) } + /** Saves a new logs collection [configuration] associated with this widget. */ private fun saveConfiguration(configuration: LogCollectionConfiguration) { val options = ReplaceOptions().upsert(true) database ?.getCollection(CONFIGURATION_COLLECTION_NAME) ?.replaceOne( - eq("_id", config.id), - configuration.toDocument(), - options + eq(Log.ID, config.id), + configuration.toDocument(), + options ) } // MongoDB - logs + /** Removes all logs from the collection. */ private fun removeAllLogs() { - logsCollection?.deleteMany(Document()) + logsCollection?.drop() } - private fun downloadInsertLogs(seq: Long, skipFirstLines: Int? = null): Int { + /** Removes the [n] first logs (ordered by their sequence number). */ + private fun removeFirstLogs(n: Long) { + val collection = logsCollection ?: return + val ids = collection + .find() + .sort(descending(Log.SEQ)) + .limit(n.toInt()) + .mapNotNull { it?.getObjectId(Log.ID) } + try { + val result = collection.deleteMany(`in`(Log.ID, ids)) + LOGGER.debug("Deleted ${result.deletedCount} first logs") + } catch (exception: MongoException) { + LOGGER.error("Cannot delete first logs: $exception") + } + } + + /** Removes logs when they are too old, there are too many of them or they take too much space. */ + private fun removeOldLogs() { + val database = database ?: return + val logsCollection = logsCollection ?: return + + // Delete old logs + val now = Instant.now().epochSecond + val beforeTimestamp = now - (config.expirationDays * DAY_TO_TIMESTAMP) + try { + val result = logsCollection.deleteMany(lt(Log.INSERTED_ON, beforeTimestamp)) + LOGGER.debug("Deleted ${result.deletedCount} old logs") + } catch (exception: MongoException) { + LOGGER.error("Cannot delete old logs: $exception") + } + + // Delete too many lines + val toDelete = logsCollection.countDocuments() - config.logLines + if (toDelete > 0) { + removeFirstLogs(toDelete) + } + + // Delete logs when they take too much space + val size = database + .runCommand(Document(STATS_COMMAND, config.id + LOGS_COLLECTION_SUFFIX)) + .getInteger(STATS_SIZE) ?: 0 + val desiredSize = config.fileSizeMB * MB_TO_KB + if (size > 0 && size > desiredSize) { + var deleteFactor = ((size - desiredSize).toDouble() / desiredSize.toLong()) + val logCount = logsCollection.countDocuments() + val toDelete = (logCount.toDouble() * deleteFactor).toLong() + if (toDelete > 0) { + removeFirstLogs(toDelete) + } + } + } + + /** Downloads new logs and inserts the to the database. */ + private fun downloadInsertLogs(seq: Long, skipFirstLines: Long? = null): Long { var sequence = seq val logs = connection .getLogs(skipFirstLines) @@ -67,19 +127,20 @@ class MongoLogStorage( } logsCollection?.insertMany(logs.map { it.toDocument() }) - // TODO: Check for the limit of the number of lines - return logs.size + return logs.size.toLong() } + /** Returns all logs for this instance, sorted by their sequence number. */ private val logs: List get() = logsCollection ?.find() - ?.sort(descending("seq")) + ?.sort(descending(Log.SEQ)) ?.mapNotNull { it } ?.mapNotNull { Log.from(it) } ?: emptyList() + /** Prepares a JSON response to be displayed */ private fun prepareResponse(): JsonObject { return JsonObject(mapOf( "variableFields" to parser.variableFields, @@ -87,23 +148,18 @@ class MongoLogStorage( )) } + /** Updates the logs and sends them to the widget. */ fun updateLogs() { - // Get the last read line from the configuration val storageConfig = collectionConfiguration var lastLine = storageConfig?.lastLine ?: 0 var seq = storageConfig?.seq ?: 0 - println("LASTLINE: $lastLine") - println("HAVE VERTX A? $vertx") // Get the length of the file coroutineScope.launch { - println("COROUTINE START") - println("HAVE VERTX B? $vertx") // Get the number of lines in the file val fileLineCount = connection.getNumberOfLines() ?: 0 - println("COROUTINE END, LINES: $fileLineCount") if (fileLineCount > 0 && fileLineCount > lastLine) { // Download new logs and append @@ -116,16 +172,15 @@ class MongoLogStorage( seq = 0 val inserted = downloadInsertLogs(seq) lastLine = inserted - seq += inserted.toLong() + seq += inserted } - // else do nothing + removeOldLogs() saveConfiguration(LogCollectionConfiguration(config.id, lastLine, seq)) // Fetch the logs from database val response = prepareResponse() - println("HAVE VERTX C? $vertx") - vertx?.eventBus()?.send("event.widget.widget1_alt", response) + vertx?.eventBus()?.send(config.eventBusAddress, response) } } @@ -133,6 +188,10 @@ class MongoLogStorage( private const val DATABASE_NAME: String = "logs" private const val CONFIGURATION_COLLECTION_NAME: String = "config" private const val LOGS_COLLECTION_SUFFIX: String = "_logs" + private const val STATS_COMMAND: String = "collStats" + private const val STATS_SIZE: String = "size" + private const val MB_TO_KB: Long = 1024 + private const val DAY_TO_TIMESTAMP = 24 * 60 * 60 private var mongoClient: MongoClient? = null private val client: MongoClient? @@ -144,7 +203,7 @@ class MongoLogStorage( mongoClient = MongoClients.create("mongodb://root:root@mongo:27017/") return mongoClient } catch (exception: MongoException) { - println("EXCEPTION: $exception") + LOGGER.error("Cannot create a mongo client: $exception") } return null } @@ -152,6 +211,7 @@ class MongoLogStorage( private val database: MongoDatabase? get() = client?.getDatabase(DATABASE_NAME) + private val LOGGER: Logger = LoggerFactory.getLogger(MongoLogStorage::class.java) val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 926a48c8c..c2568603f 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -130,7 +130,11 @@ class SSHCoroutineClient(private val config: JsonObject) { // jsch.setKnownHosts("~/.ssh/known_hosts") for security reasons this should be used val session = SessionStrategyFactory(jsch).create(authData).initSession() session.setConfig("StrictHostKeyChecking", "no") // not secure - session.connect(CogboardConstants.Props.SSH_TIMEOUT) + try { + session.connect(CogboardConstants.Props.SSH_TIMEOUT) + } catch (exception: JSchException) { + LOGGER.error("Cannot connect to SSH server: $exception") + } this.session = session this.jsch = jsch } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index 5e40e4115..a0742b306 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -31,6 +31,6 @@ abstract class ConnectionStrategyInt { return setOf(AuthenticationType.BASIC) } - abstract fun getNumberOfLines(): Int? - abstract fun getLogs(skipFirstLines: Int?): Collection + abstract fun getNumberOfLines(): Long? + abstract fun getLogs(skipFirstLines: Long?): Collection } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index ad5f41a90..95dfdb11e 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -44,16 +44,16 @@ open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : class SSHConnectionStrategyInt(val config: JsonObject) : ConnectionStrategyInt() { - override fun getNumberOfLines(): Int? { + override fun getNumberOfLines(): Long? { val logFilePath = config.getString(Props.PATH) ?: return null return SSHCoroutineClient(prepareConfig(config)) .executeAndClose("wc -l < $logFilePath") ?.trim() - ?.toIntOrNull() + ?.toLongOrNull() } - override fun getLogs(skipFirstLines: Int?): Collection { + override fun getLogs(skipFirstLines: Long?): Collection { val logFilePath = config.getString(Props.PATH) ?: return emptyList() val command = skipFirstLines?.let { "tail -n +${it + 1} $logFilePath" } ?: "cat $logFilePath" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 12cbe30a1..c08ea9d15 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -5,15 +5,13 @@ import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.logStorage.MongoLogStorage import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyInt import com.cognifide.cogboard.widget.connectionStrategy.SSHConnectionStrategyInt -import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategy +import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParser import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategyFactory import com.cognifide.cogboard.widget.type.logviewer.logparser.MockLogParser import io.vertx.core.Vertx -import io.vertx.core.eventbus.Message import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration @@ -24,28 +22,23 @@ class LogViewerWidget( serv: BoardsConfigService ) : BaseWidget(vertx, config, serv) { private val address = config.endpointProp(Props.URL) - private var consumer: MessageConsumer<*>? = null - private val connectionStrategy: ConnectionStrategy = determineConnectionStrategy() + private var consumer: MessageConsumer? = null private val connectionStrategyInt: ConnectionStrategyInt = SSHConnectionStrategyInt(config) - private val logParsingStrategy: LogParserStrategy = determineLogParsingStrategy() + private val logParser: LogParser = MockLogParser() private val logStorage = MongoLogStorage( buildConfiguration(config), connectionStrategyInt, - MockLogParser() + logParser ) override fun start(): Widget { vertx.deployVerticle(logStorage) - consumer = connectionStrategy.getConsumer(eventBusAddress) - consumer!!.handler { - handleResponse(it) - } - vertx.eventBus().consumer(eventBusAddress + "_alt").handler { message -> - message?.body()?.let { - val s = it.toString() + " " - println("HANDLED $s") + consumer = vertx.eventBus() + .consumer(eventBusAddress) + .handler { message -> + println("Sending: ${message.body()}") + message?.body()?.let { send(it) } } - } return super.start() } @@ -57,34 +50,12 @@ class LogViewerWidget( override fun updateState() { if (address.isNotBlank()) { - connectionStrategy.sendRequest(address, config) logStorage.updateLogs() } else { sendConfigurationError("Endpoint URL is blank") } } - private fun handleResponse(response: Message<*>) { - val responseBody = response.body() - if (responseBody is JsonObject) { - handleHttpResponse(responseBody) - } else { - send(prepareLogs(connectionStrategy.handleResponse(responseBody))) - } - } - - private fun handleHttpResponse(responseBody: JsonObject) { - if (checkAuthorized(responseBody)) { - send(prepareLogs(connectionStrategy.handleResponse(responseBody))) - } - } - - private fun prepareLogs(logs: String): JsonObject { - var logLines = logs.split("\n") - logLines = logLines.filter { it.isNotEmpty() } - return JsonObject().put("logs", logParsingStrategy.parseLines(logLines)) - } - private fun determineConnectionStrategy() = ConnectionStrategyFactory(config, address) .addVertxInstance(vertx) @@ -98,9 +69,10 @@ class LogViewerWidget( private fun buildConfiguration(config: JsonObject): LogStorageConfiguration { return LogStorageConfiguration( config.getString(Props.ID) ?: "0", - config.getInteger(Props.LOG_LINES) ?: 100, - config.getInteger(Props.LOG_FILE_SIZE) ?: 50, - config.getInteger(Props.LOG_EXPIRATION_DAYS) ?: 5 + config.getLong(Props.LOG_LINES) ?: 100, + config.getLong(Props.LOG_FILE_SIZE) ?: 50, + config.getLong(Props.LOG_EXPIRATION_DAYS) ?: 5, + eventBusAddress ) } } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index a56b7d487..efb4f9bd1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,13 +1,5 @@ import React, { useState } from 'react'; -import { - string, - number, - bool, - shape, - oneOfType, - arrayOf, - objectOf -} from 'prop-types'; +import { string, number, bool, shape, oneOfType, arrayOf } from 'prop-types'; import { AccordionSummary, AccordionDetails } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { @@ -18,27 +10,20 @@ import { } from './styled'; import getGridTemplate from './helpers'; -export default function LogEntry({ type, date, additionalData, variableData }) { +export default function LogEntry({ type, date, variableData, template }) { const [expanded, setExpanded] = useState(false); - const AdditionalData = ({ names }) => { - const additionalDataNames = additionalData && Object.keys(additionalData); - return ( -
- {additionalDataNames.map((name, index) => ( - {names ? name : `${additionalData[name]}`} - ))} -
- ); - }; - const VariablePart = ({ description }) => { - const variableFieldsTemplate = getGridTemplate(variableData.template); - const data = description ? variableData.description : variableData.headers; + const variableFieldsTemplate = getGridTemplate(template); return ( - - {data?.map((text, index) => ( - {text} + + {variableData.map((entry, index) => ( + + {description ? entry.description : entry.header} + ))} ); @@ -58,8 +43,6 @@ export default function LogEntry({ type, date, additionalData, variableData }) { - - @@ -70,20 +53,16 @@ export default function LogEntry({ type, date, additionalData, variableData }) { LogEntry.propTypes = { type: string, date: string.isRequired, - additionalData: objectOf(oneOfType([string, number, bool])), - variableData: shape({ - template: arrayOf(string).isRequired, - headers: arrayOf(oneOfType([string, number, bool])).isRequired, - description: arrayOf(oneOfType([string, number, bool])).isRequired - }) + variableData: arrayOf( + shape({ + header: arrayOf(oneOfType([string, number, bool])).isRequired, + description: arrayOf(oneOfType([string, number, bool])).isRequired + }) + ) }; LogEntry.defaultProps = { type: 'info', - variableData: { - template: [], - headers: [], - description: [] - }, - additionalData: {} + date: '0', + variableData: [] }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index c85f9192a..1640f5726 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -35,10 +35,11 @@ export default function LogList({ logs, template }) { {logs?.map((log, index) => ( ))}
diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 62669eaf6..3a1b9db7d 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -28,6 +28,7 @@ export const VariableGridSchema = styled.div( width: 100%; display: grid; grid-template-columns: ${props.template}; + ${props.skipColumns ? 'grid-column: 3 / 4' : ''} ` ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index d7336f061..68a299ab7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -13,10 +13,12 @@ const LogViewerWidget = ({ id }) => { useEffect(() => console.log(widgetData), [widgetData]); const logs = widgetData.content?.logs; + const template = widgetData.content?.variableFields; + console.log(widgetData); return ( - {logs && } + {logs && } ); }; From 5294bf250ec646ec748c0c044578f929d7fab8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sat, 20 Nov 2021 19:16:29 +0100 Subject: [PATCH 068/226] Minor SSH adjustments [#416] --- .../src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt | 3 +-- ssh/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 1163deb8e..fd394f369 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -75,9 +75,8 @@ class SSHClient : AbstractVerticle() { private fun initSSHSession(authData: SSHAuthData) { jsch = JSch() - // jsch.setKnownHosts("~/.ssh/known_hosts") for security reasons this should be used session = SessionStrategyFactory(jsch).create(authData).initSession() - session.setConfig("StrictHostKeyChecking", "no") // not secure + session.setConfig("StrictHostKeyChecking", "no") session.connect(CogboardConstants.Props.SSH_TIMEOUT) } diff --git a/ssh/Dockerfile b/ssh/Dockerfile index 34debc3ee..ca54c678e 100644 --- a/ssh/Dockerfile +++ b/ssh/Dockerfile @@ -1,6 +1,6 @@ FROM lscr.io/linuxserver/openssh-server -ENV PUBLIC_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICLzgU4PuqFGnQCd5A5Gl30ssE3b9fW7gcyvES4xHHUo mock@openssh-server" +ENV PUBLIC_KEY="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCuNrl6m54JiRbCtqQEQlzB3Lzaj4wzNirMvxJy9lRBePNd1Asqob6kTY2DwKV2n79f4A8vmpz+Q/MMkljFSEARX/H9qP/KfZx1OPNFzxx69LVQJZQCsLwabVHrKqGFu+wfzWWA1pBgTo7EiIsTkMP/RSqZpt4fO8F06viUMguTeSM40JaG8oC2jnOMSL0ZHB8Ng/YirGu4L04JjgjW0k23SAO8v4IJPOku5l4lXLqJYkAVkZwgrD9mzy1MbtuOiNZcMkLW9PPY17MYRrLZyu1rQb9hnZqT2Q4Y0dxYG5PDbfHhipeNr6gDRalfzztw3KBMVNflD8rNcGN3ibw2u4XSmF3yIpLcvo195mYeg6nVmj6OTgdGuy5Jkafe5EzrEhFMm2Fgj7feKKztzhpDK2r+HStSN3BRISmknF/QWMmWRXiIIrWxeVPqUoJsSLPhQQq0++e8IZ9P9Q9g2etsu2nL3jHIB9JbM+WbkapvTs2Z7k7h3m/Tl/dENcKlrkIlgHs=" ENV PASSWORD_ACCESS=true ENV USER_NAME=mock ENV USER_PASSWORD=TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E From 20b51625e3dada162d3abb3aa217e226faec59bc Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 20 Nov 2021 19:39:47 +0100 Subject: [PATCH 069/226] Add tooltips #389 --- .../types/LogViewerWidget/LogList/index.js | 3 +- .../AdvancedFiltersMenu/FilterForm.js | 7 ++-- .../FilterPicker/AdvancedFiltersMenu/index.js | 36 ++++++++++++------- .../AdvancedFiltersMenu/styled.js | 11 ++++++ .../Toolbar/FilterPicker/helpers.js | 3 ++ .../Toolbar/FilterPicker/index.js | 30 +++++++++------- .../widgets/types/LogViewerWidget/index.js | 13 ++++--- 7 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/styled.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index ca2b442b0..1b61178ce 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -10,10 +10,11 @@ import { VariableGridSchema } from './styled'; import getGridTemplate from './helpers'; +import { getFilters } from '../Toolbar/FilterPicker/helpers'; export default function LogList({ widgetLocalStorage, logs, template }) { const theme = useTheme(); - const filters = widgetLocalStorage?.regExpFilters || []; + const filters = getFilters(widgetLocalStorage); const filterByRegExp = (log, filters) => { let result = true; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js index dff82d566..57fc83767 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js @@ -1,9 +1,10 @@ import React from 'react'; import { createValidationSchema } from '../../../../../../validation'; import { useFormData } from '../../../../../../../hooks'; -import DynamicForm from '../../../../../../DynamicForm'; + import { Button } from '@material-ui/core'; -import CancelButton from '../../../../../../CancelButton'; +import DynamicForm from '../../../../../../DynamicForm'; +import { StyledCancelButton } from './styled'; const FilterForm = ({ filters, @@ -47,7 +48,7 @@ const FilterForm = ({ > Save - diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index a83be18e5..3125d313b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { useToggle } from '../../../../../../../hooks'; -import { setFilters } from '../helpers'; +import { getFilters, setFilters } from '../helpers'; import { Button, @@ -8,17 +8,19 @@ import { ListItem, ListItemText, ListItemSecondaryAction, - Switch + Switch, + Tooltip } from '@material-ui/core'; import AppDialog from '../../../../../../AppDialog'; import AddItem from '../../../../../../AddItem'; import EditFilter from './EditFilter'; import DeleteItem from '../../../../../../DeleteItem'; import FilterForm from './FilterForm'; +import { StyledExitButton } from './styled'; const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); - const filters = widgetLocalStorage.get()?.regExpFilters || []; + const filters = getFilters(widgetLocalStorage); const addFilter = values => { const maxId = filters.reduce((acc, { id }) => (id > acc ? id : acc), 0); @@ -62,9 +64,14 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { editAction, deleteAction ) => - items.map(({ id, label, checked }) => ( + items.map(({ id, label, checked, regExp }) => ( - + + + { itemName={name} deleteAction={deleteAction} /> - handleSwitch(id)} - color="secondary" - /> + + handleSwitch(id)} + color="secondary" + /> + )); @@ -105,7 +117,7 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { - + ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/styled.js new file mode 100644 index 000000000..5b42fa61d --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/styled.js @@ -0,0 +1,11 @@ +import styled from '@emotion/styled/macro'; +import { Button } from '@material-ui/core'; +import CancelButton from '../../../../../../CancelButton'; + +export const StyledExitButton = styled(Button)` + margin-top: 12px; +`; + +export const StyledCancelButton = styled(CancelButton)` + margin-left: 20px; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js index 57cdf52a6..6f33b76aa 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js @@ -5,3 +5,6 @@ export const setFilters = ( const newWidgetData = { ...localStorage(), regExpFilters: filters }; setLocalStorage(newWidgetData); }; + +export const getFilters = widgetLocalStorage => + widgetLocalStorage.get()?.regExpFilters || []; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 2e7f77020..2cb30fd8c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { setFilters } from './helpers'; +import { getFilters, setFilters } from './helpers'; import logLevels from '../../logLevels'; @@ -8,14 +8,15 @@ import { Chip, MenuItem, FormControl, - InputLabel + InputLabel, + Tooltip } from '@material-ui/core'; import { ScrollableBox } from './styled'; import ToolbarGroup from '../ToolbarGroup'; import AdvancedFiltersMenu from './AdvancedFiltersMenu'; const FilterPicker = ({ widgetLocalStorage }) => { - const regExpFilters = widgetLocalStorage.get()?.regExpFilters || []; + const regExpFilters = getFilters(widgetLocalStorage); const [logLevel, setLogLevel] = useState('info'); const handleSelection = selectedList => @@ -49,15 +50,20 @@ const FilterPicker = ({ widgetLocalStorage }) => { onChange={e => handleSelection(e.target.value)} renderValue={selected => ( - {selected.map(({ id, label }) => ( - handleDelete(id)} - onMouseDown={e => e.stopPropagation()} - style={{ marginRight: '4px' }} - size="small" - /> + {selected.map(({ id, label, regExp }) => ( + + handleDelete(id)} + onMouseDown={e => e.stopPropagation()} + style={{ marginRight: '4px' }} + size="small" + /> + ))} )} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 9258d7817..299f3fa85 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -14,17 +14,16 @@ const LogViewerWidget = ({ id }) => { ); useEffect(() => console.log(widgetData), [widgetData]); - const [widgetLocalStorage, setWidgetLocalStorage] = useLocalStorage(id); + const [widgetLocalStorageData, setWidgetLocalStorage] = useLocalStorage(id); + const widgetLocalStorage = { + get: () => widgetLocalStorageData, + set: setWidgetLocalStorage + }; const logs = widgetData.content?.logs; return ( - widgetLocalStorage, - set: setWidgetLocalStorage - }} - /> + {logs && ( Date: Sat, 20 Nov 2021 19:49:18 +0100 Subject: [PATCH 070/226] Delete HTTP connection strategy --- .../com/cognifide/cogboard/logStorage/Log.kt | 34 ++++---- .../logStorage/LogCollectionConfiguration.kt | 17 ++-- .../{MongoLogStorage.kt => LogStorage.kt} | 22 ++--- .../connectionStrategy/ConnectionStrategy.kt | 20 +---- .../ConnectionStrategyFactory.kt | 81 ------------------- .../HttpConnectionStrategy.kt | 52 ------------ .../SSHConnectionStrategy.kt | 39 +-------- .../widget/type/logviewer/LogViewerWidget.kt | 59 +++++++------- .../logviewer/logparser/LogParserFactory.kt | 16 ++++ .../logviewer/logparser/LogParserStrategy.kt | 17 ---- .../logparser/LogParserStrategyFactory.kt | 16 ---- .../type/logviewer/logparser/MockLogParser.kt | 32 ++++---- .../logparser/MockLogParserStrategy.kt | 30 ------- .../type/logviewer/logparser/ParsedLog.kt | 63 --------------- .../logparser/MockLogParserStrategyTest.kt | 30 +++---- 15 files changed, 120 insertions(+), 408 deletions(-) rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/{MongoLogStorage.kt => LogStorage.kt} (91%) delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserFactory.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt index 6c6a7c6ea..a7b402247 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt @@ -4,7 +4,9 @@ import org.bson.Document import org.bson.types.ObjectId import io.vertx.core.json.JsonObject import java.time.Instant -import java.util.* +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter data class LogVariableData( val header: String, @@ -48,7 +50,9 @@ data class Log( ID to id.toHexString(), SEQ to seq, INSERTED_ON to insertedOn, - DATE to date, + DATE to (LocalDateTime + .ofEpochSecond(date, 0, ZoneOffset.UTC) + .format(DateTimeFormatter.ISO_DATE) ?: ""), TYPE to type, VARIABLE_DATA to variableData.map { it.toJson() } )) @@ -62,19 +66,21 @@ data class Log( const val VARIABLE_DATA = "variableData" fun from(document: Document): Log? { - val id = document.getObjectId(ID) - val seq = document.getLong(SEQ) - val insertedOn = document.getLong(INSERTED_ON) - val date = document.getLong(DATE) - val type = document.getString(TYPE) - - if (arrayOf(id, seq, insertedOn, date, type).contains(null)) { return null } + try { + val id = document.getObjectId(ID) + val seq = document.getLong(SEQ) + val insertedOn = document.getLong(INSERTED_ON) + val date = document.getLong(DATE) + val type = document.getString(TYPE) - val variableData = document - .getList(VARIABLE_DATA, Document::class.java) - ?.mapNotNull { it } - ?.mapNotNull { LogVariableData.from(it) } ?: listOf() - return Log(id, seq, insertedOn, date, type, variableData) + val variableData = document + .getList(VARIABLE_DATA, Document::class.java) + ?.mapNotNull { it } + ?.mapNotNull { LogVariableData.from(it) } ?: listOf() + return Log(id, seq, insertedOn, date, type, variableData) + } catch (_: NullPointerException) { + return null + } } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt index fc32ee5fb..e662be8d0 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt @@ -16,17 +16,20 @@ data class LogCollectionConfiguration( fun toDocument() = Document(map) companion object { - const val CONFIG_ID: String = "config" fun from(document: Document): LogCollectionConfiguration? { - val id = document.getString("_id") - val lastLine = document.getLong("lastLine") - val seq = document.getLong("seq") + try { + val id = document.getString("_id") + val lastLine = document.getLong("lastLine") + val seq = document.getLong("seq") - if (arrayOf(id, lastLine, seq).contains(null)) { + if (arrayOf(id, lastLine, seq).contains(null)) { + return null + } + + return LogCollectionConfiguration(id, lastLine, seq) + } catch (_: NullPointerException) { return null } - - return LogCollectionConfiguration(id, lastLine, seq) } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt similarity index 91% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index df25d9604..f93e78fdc 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/MongoLogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -1,13 +1,15 @@ package com.cognifide.cogboard.logStorage -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyInt +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParser import com.mongodb.client.MongoClient import com.mongodb.MongoException import com.mongodb.client.MongoClients import com.mongodb.client.MongoCollection import com.mongodb.client.MongoDatabase -import com.mongodb.client.model.Filters.* +import com.mongodb.client.model.Filters.eq +import com.mongodb.client.model.Filters.lt +import com.mongodb.client.model.Filters.`in` import com.mongodb.client.model.Sorts.descending import com.mongodb.client.model.ReplaceOptions import io.vertx.core.AbstractVerticle @@ -24,9 +26,9 @@ import main.kotlin.com.cognifide.cogboard.logStorage.Log import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration import java.time.Instant -class MongoLogStorage( +class LogStorage( private val config: LogStorageConfiguration, - private val connection: ConnectionStrategyInt, + private val connection: ConnectionStrategy, private val parser: LogParser ) : AbstractVerticle() { @@ -95,9 +97,9 @@ class MongoLogStorage( } // Delete too many lines - val toDelete = logsCollection.countDocuments() - config.logLines - if (toDelete > 0) { - removeFirstLogs(toDelete) + val redundantLines = logsCollection.countDocuments() - config.logLines + if (redundantLines > 0) { + removeFirstLogs(redundantLines) } // Delete logs when they take too much space @@ -106,7 +108,7 @@ class MongoLogStorage( .getInteger(STATS_SIZE) ?: 0 val desiredSize = config.fileSizeMB * MB_TO_KB if (size > 0 && size > desiredSize) { - var deleteFactor = ((size - desiredSize).toDouble() / desiredSize.toLong()) + val deleteFactor = ((size - desiredSize).toDouble() / desiredSize) val logCount = logsCollection.countDocuments() val toDelete = (logCount.toDouble() * deleteFactor).toLong() if (toDelete > 0) { @@ -194,6 +196,7 @@ class MongoLogStorage( private const val DAY_TO_TIMESTAMP = 24 * 60 * 60 private var mongoClient: MongoClient? = null + /** Returns a shared instance of the Mongo client. */ private val client: MongoClient? get() { if (mongoClient != null) { @@ -208,10 +211,11 @@ class MongoLogStorage( return null } + /** Returns a database for storing logs and collection configurations. */ private val database: MongoDatabase? get() = client?.getDatabase(DATABASE_NAME) - private val LOGGER: Logger = LoggerFactory.getLogger(MongoLogStorage::class.java) + private val LOGGER: Logger = LoggerFactory.getLogger(LogStorage::class.java) val coroutineScope = CoroutineScope(Job() + Dispatchers.IO) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt index a0742b306..f257fee48 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategy.kt @@ -2,27 +2,9 @@ package com.cognifide.cogboard.widget.connectionStrategy import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.http.auth.AuthenticationType -import io.vertx.core.Vertx -import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject -abstract class ConnectionStrategy(protected val vertx: Vertx, protected val eventBusAddress: String) { - protected fun JsonObject.endpointProp(prop: String): String { - return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" - } - - protected open fun authenticationTypes(): Set { - return setOf(AuthenticationType.BASIC) - } - - abstract fun sendRequest(address: String, arguments: JsonObject) - - abstract fun getConsumer(eventBusAddress: String): MessageConsumer<*> - - abstract fun handleResponse(response: Any): String -} - -abstract class ConnectionStrategyInt { +abstract class ConnectionStrategy { protected fun JsonObject.endpointProp(prop: String): String { return this.getJsonObject(CogboardConstants.Props.ENDPOINT_LOADED)?.getString(prop) ?: "" } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt deleted file mode 100644 index b734cb998..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/ConnectionStrategyFactory.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.cognifide.cogboard.widget.connectionStrategy - -import com.cognifide.cogboard.CogboardConstants.ConnectionType.Companion.HTTP -import com.cognifide.cogboard.CogboardConstants.ConnectionType.Companion.SSH -import com.cognifide.cogboard.CogboardConstants.Props -import io.vertx.core.Vertx -import io.vertx.core.json.JsonObject -import java.net.URI - -class ConnectionStrategyFactory( - config: JsonObject, - uri: String -) { - private val connectionType: String - private lateinit var vertx: Vertx - private lateinit var eventBusAddress: String - - init { - connectionType = determineConnectionType(uri, config) - } - - fun addVertxInstance(vertx: Vertx): ConnectionStrategyFactory { - this.vertx = vertx - return this - } - - fun addEventBusAddress(eventBusAddress: String): ConnectionStrategyFactory { - this.eventBusAddress = eventBusAddress - return this - } - - private fun determineConnectionType(uri: String, config: JsonObject): String { - val url = URI.create(uri) - return when (url.scheme) { - "http", "https" -> HTTP - "ssh" -> { - prepareSshConfig(url, config) - SSH - } - else -> { - throw UnknownConnectionTypeException("Unknown strategy type") - } - } - } - - private fun prepareSshConfig(uri: URI, config: JsonObject) { - config.put(Props.SSH_HOST, uri.host) - uri.port.let { - if (it != -1) config.put(Props.SSH_PORT, uri.port) - } - } - - fun checkRequiredParameters() { - var message = "" - when { - !::vertx.isInitialized -> message = "Vertx instance not passed to builder" - !::eventBusAddress.isInitialized -> message = "Eventbus address not passed to builder" - } - - if (message.isNotBlank()) { - throw MissingBuilderParametersException(message) - } - } - - fun build(): ConnectionStrategy { - checkRequiredParameters() - return when (connectionType) { - HTTP -> HttpConnectionStrategy(vertx, eventBusAddress) - SSH -> SSHConnectionStrategy(vertx, eventBusAddress) - else -> throw UnknownConnectionTypeException("Unknown strategy type") - } - } -} - -class UnknownConnectionTypeException( - message: String? -) : RuntimeException(message) - -class MissingBuilderParametersException( - message: String? -) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt deleted file mode 100644 index c4dc5500a..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/HttpConnectionStrategy.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.cognifide.cogboard.widget.connectionStrategy - -import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.CogboardConstants.Event -import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.GET -import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.PUT -import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.POST -import com.cognifide.cogboard.CogboardConstants.RequestMethod.Companion.DELETE -import io.vertx.core.Vertx -import io.vertx.core.eventbus.MessageConsumer -import io.vertx.core.json.Json -import io.vertx.core.json.JsonObject - -class HttpConnectionStrategy(vertx: Vertx, eventBusAddress: String) : - ConnectionStrategy(vertx, eventBusAddress) { - override fun sendRequest(address: String, arguments: JsonObject) { - when (arguments.getString(Props.LOG_REQUEST_TYPE, "")) { - GET -> vertx.eventBus().send(Event.HTTP_GET, getProps(arguments)) - PUT -> vertx.eventBus().send(Event.HTTP_PUT, putProps(arguments)) - POST -> vertx.eventBus().send(Event.HTTP_POST, postProps(arguments)) - DELETE -> vertx.eventBus().send(Event.HTTP_DELETE, basicProps(arguments)) - } - } - - override fun getConsumer(eventBusAddress: String): MessageConsumer<*> = - vertx.eventBus().consumer(eventBusAddress) - - override fun handleResponse(response: Any): String = - (response as JsonObject).getString(Props.LOG_LINES) - - private fun basicProps(props: JsonObject): JsonObject = - JsonObject() - .put(Props.URL, props.endpointProp(Props.URL)) - .put(Props.EVENT_ADDRESS, eventBusAddress) - .put(Props.USER, props.endpointProp(Props.USER)) - .put(Props.PASSWORD, props.endpointProp(Props.PASSWORD)) - .put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) - .put(Props.CONTENT_TYPE, props.endpointProp(Props.CONTENT_TYPE)) - - private fun getProps(props: JsonObject): JsonObject = - basicProps(props) - .put(Props.REQUEST_ID, props.getValue(Props.REQUEST_ID, "")) - .put(Props.TOKEN, props.endpointProp(Props.TOKEN)) - - private fun putProps(props: JsonObject): JsonObject = - basicProps(props) - .put(Props.BODY, props.getJsonObject(Props.BODY)) - - private fun postProps(props: JsonObject): JsonObject = - basicProps(props) - .put(Props.BODY, props.getJsonObject(Props.BODY)) -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index 95dfdb11e..768b2058c 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -1,48 +1,11 @@ package com.cognifide.cogboard.widget.connectionStrategy -import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.ssh.SSHCoroutineClient -import io.vertx.core.Vertx -import io.vertx.core.buffer.Buffer -import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.Json import io.vertx.core.json.JsonObject -import java.nio.charset.Charset -open class SSHConnectionStrategy(vertx: Vertx, eventBusAddress: String) : - ConnectionStrategy(vertx, eventBusAddress) { - override fun sendRequest(address: String, arguments: JsonObject) { - val config = prepareConfig(arguments) - vertx.eventBus().send(CogboardConstants.Event.SSH_COMMAND, config) - } - - override fun getConsumer(eventBusAddress: String): MessageConsumer<*> = - vertx.eventBus().consumer(eventBusAddress) - - override fun handleResponse(response: Any): String = - (response as Buffer).toString(Charset.defaultCharset()) - - private fun prepareConfig(config: JsonObject): JsonObject { - val tmpConfig = prepareConfigLines(config, - Props.USER, Props.PASSWORD, Props.TOKEN, Props.SSH_KEY, Props.SSH_KEY_PASSPHRASE - ) - - tmpConfig.getString(Props.AUTHENTICATION_TYPES) - ?: config.put(Props.AUTHENTICATION_TYPES, Json.encode(authenticationTypes())) - tmpConfig.put(Props.EVENT_ADDRESS, eventBusAddress) - return tmpConfig - } - - private fun prepareConfigLines(config: JsonObject, vararg fields: String): JsonObject { - for (field in fields) { - config.getString(field) ?: config.put(field, config.endpointProp(field)) - } - return config - } -} - -class SSHConnectionStrategyInt(val config: JsonObject) : ConnectionStrategyInt() { +class SSHConnectionStrategy(val config: JsonObject) : ConnectionStrategy() { override fun getNumberOfLines(): Long? { val logFilePath = config.getString(Props.PATH) ?: return null diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index c08ea9d15..ef670a718 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -2,19 +2,17 @@ package com.cognifide.cogboard.widget.type.logviewer import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService -import com.cognifide.cogboard.logStorage.MongoLogStorage +import com.cognifide.cogboard.logStorage.LogStorage import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyFactory -import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategyInt -import com.cognifide.cogboard.widget.connectionStrategy.SSHConnectionStrategyInt -import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParser -import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategyFactory -import com.cognifide.cogboard.widget.type.logviewer.logparser.MockLogParser +import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy +import com.cognifide.cogboard.widget.connectionStrategy.SSHConnectionStrategy +import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserFactory import io.vertx.core.Vertx import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonObject import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration +import java.net.URI class LogViewerWidget( vertx: Vertx, @@ -23,12 +21,10 @@ class LogViewerWidget( ) : BaseWidget(vertx, config, serv) { private val address = config.endpointProp(Props.URL) private var consumer: MessageConsumer? = null - private val connectionStrategyInt: ConnectionStrategyInt = SSHConnectionStrategyInt(config) - private val logParser: LogParser = MockLogParser() - private val logStorage = MongoLogStorage( + private val logStorage = LogStorage( buildConfiguration(config), - connectionStrategyInt, - logParser + determineConnectionStrategy(), + determineLogParsingStrategy() ) override fun start(): Widget { @@ -36,7 +32,6 @@ class LogViewerWidget( consumer = vertx.eventBus() .consumer(eventBusAddress) .handler { message -> - println("Sending: ${message.body()}") message?.body()?.let { send(it) } } return super.start() @@ -56,23 +51,33 @@ class LogViewerWidget( } } - private fun determineConnectionStrategy() = - ConnectionStrategyFactory(config, address) - .addVertxInstance(vertx) - .addEventBusAddress(eventBusAddress) - .build() - - private fun determineLogParsingStrategy() = - LogParserStrategyFactory() - .build(LogParserStrategyFactory.MOCK) - private fun buildConfiguration(config: JsonObject): LogStorageConfiguration { return LogStorageConfiguration( - config.getString(Props.ID) ?: "0", - config.getLong(Props.LOG_LINES) ?: 100, - config.getLong(Props.LOG_FILE_SIZE) ?: 50, - config.getLong(Props.LOG_EXPIRATION_DAYS) ?: 5, + config.getString(Props.ID) ?: DEFAULT_ID, + config.getLong(Props.LOG_LINES) ?: DEFAULT_LOG_LINES, + config.getLong(Props.LOG_FILE_SIZE) ?: DEFAULT_LOG_FILE_SIZE, + config.getLong(Props.LOG_EXPIRATION_DAYS) ?: DEFAULT_LOG_EXPIRATION_DAYS, eventBusAddress ) } + + private fun determineConnectionStrategy(): ConnectionStrategy { + return when (URI.create(address).scheme) { + "ssh" -> SSHConnectionStrategy(config) + else -> throw UnknownConnectionTypeException("Connection type not supported") + } + } + + private fun determineLogParsingStrategy() = + LogParserFactory() + .build(LogParserFactory.Type.MOCK) + + companion object { + private const val DEFAULT_ID = "0" + private const val DEFAULT_LOG_LINES = 100L + private const val DEFAULT_LOG_FILE_SIZE = 50L + private const val DEFAULT_LOG_EXPIRATION_DAYS = 5L + } } + +class UnknownConnectionTypeException(message: String?) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserFactory.kt new file mode 100644 index 000000000..b5a17be8a --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserFactory.kt @@ -0,0 +1,16 @@ +package com.cognifide.cogboard.widget.type.logviewer.logparser + +class LogParserFactory { + enum class Type { + MOCK + } + + fun build(type: Type): LogParser { + return when (type) { + Type.MOCK -> MockLogParser() + else -> throw UnknownParserTypeException("Unknown log parsing type") + } + } +} + +class UnknownParserTypeException(message: String) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt deleted file mode 100644 index a5c41303d..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.cognifide.cogboard.widget.type.logviewer.logparser - -import io.vertx.core.json.JsonArray -import io.vertx.core.json.JsonObject - -abstract class LogParserStrategy { - fun parseLines(logLines: Collection): JsonArray { - val resultArray = JsonArray() - for (line in logLines) { - val parsedLine = parseLine(line) - resultArray.add(parsedLine) - } - return resultArray - } - - abstract fun parseLine(logLine: String): JsonObject -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt deleted file mode 100644 index 889dcbeb6..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.cognifide.cogboard.widget.type.logviewer.logparser - -class LogParserStrategyFactory { - companion object { - const val MOCK = "mock" - } - - fun build(type: String): LogParserStrategy { - return when (type) { - MOCK -> MockLogParserStrategy() - else -> throw UnknownParserTypeException("Unknown strategy type") - } - } -} - -class UnknownParserTypeException(message: String) : RuntimeException(message) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt index 2af987b64..c992c21fb 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParser.kt @@ -2,8 +2,8 @@ package com.cognifide.cogboard.widget.type.logviewer.logparser import main.kotlin.com.cognifide.cogboard.logStorage.Log import main.kotlin.com.cognifide.cogboard.logStorage.LogVariableData -import java.sql.Timestamp import java.time.LocalDateTime +import java.time.ZoneOffset import java.time.format.DateTimeFormatter class MockLogParser : LogParser { @@ -15,23 +15,23 @@ class MockLogParser : LogParser { override fun parseLine(line: String): Log? { val groups = regex.matchEntire(line.trim())?.groups ?: return null - val date = groups[DATE]?.value - ?.let { LocalDateTime.parse(it, dateTimeFormatter) } - ?.let { Timestamp.valueOf(it)?.time } - val type = groups[TYPE]?.value - val provider = groups[PROVIDER]?.value - val message = groups[MESSAGE]?.value - - if (date == null || type == null || provider == null || message == null) { + try { + val date = LocalDateTime + .parse(groups[DATE]!!.value, dateTimeFormatter) + .toEpochSecond(ZoneOffset.UTC) + val type = groups[TYPE]!!.value + val provider = groups[PROVIDER]!!.value + val message = groups[MESSAGE]!!.value + + val variableData = listOf( + LogVariableData(provider, "No description"), + LogVariableData(message, "No message description") + ) + + return Log(date = date, type = type, variableData = variableData) + } catch (_: NullPointerException) { return null } - - val variableData = listOf( - LogVariableData(provider, "No description"), - LogVariableData(message, "No message description") - ) - - return Log(date = date, type = type, variableData = variableData) } companion object { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt deleted file mode 100644 index ab80261eb..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.cognifide.cogboard.widget.type.logviewer.logparser - -import io.vertx.core.json.JsonObject -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.TYPE -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.DATE -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.PROVIDER -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.MESSAGE - -class MockLogParserStrategy : LogParserStrategy() { - private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""" - .trimMargin() - .toRegex() - - override fun parseLine(logLine: String): JsonObject { - val groups = regex.matchEntire(logLine.trim())?.groups - - return createLogObject(groups) - } - - private fun createLogObject(groups: MatchGroupCollection?): JsonObject { - val mapOfCapturedValues = mutableMapOf() - mapOfCapturedValues[TYPE] = groups?.get(TYPE)?.value ?: "" - mapOfCapturedValues[DATE] = groups?.get(DATE)?.value ?: "" - mapOfCapturedValues[PROVIDER] = groups?.get(PROVIDER)?.value ?: "" - mapOfCapturedValues[MESSAGE] = groups?.get(MESSAGE)?.value ?: "" - - val parsedLog = ParsedLog(mapOfCapturedValues) - return parsedLog.parsedLogJson - } -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt deleted file mode 100644 index d9de522f6..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/ParsedLog.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.cognifide.cogboard.widget.type.logviewer.logparser - -import io.vertx.core.json.JsonArray -import io.vertx.core.json.JsonObject - -class ParsedLog(values: Map) { - companion object { - const val DATE = "date" - const val TYPE = "type" - const val PROVIDER = "Provider" - const val MESSAGE = "Message" - const val TEMPLATE = "template" - const val HEADERS = "headers" - const val VARIABLE_DATA = "variableData" - const val DESCRIPTION = "description" - const val ADDITIONAL_DATA = "additionalData" - const val ID = "ID" - const val IP_ADDRESS = "IP address" - const val PORT = "Port" - } - - private val _parsedLogJson = JsonObject() - private val variableData = JsonObject() - - val parsedLogJson: JsonObject - get() { - _parsedLogJson.put(VARIABLE_DATA, variableData) - return _parsedLogJson - } - - init { - values[TYPE]?.let { _parsedLogJson.put(TYPE, it) } - values[DATE]?.let { _parsedLogJson.put(DATE, it) } - values[PROVIDER]?.let { addFieldToVariableData(PROVIDER, it) } - values[MESSAGE]?.let { addFieldToVariableData(MESSAGE, it) } - addAdditionalData() - } - - private fun addFieldToVariableData(template: String, value: String) { - val templateArray = variableData.getJsonArray(TEMPLATE, JsonArray()) - val headersArray = variableData.getJsonArray(HEADERS, JsonArray()) - val descriptionArray = variableData.getJsonArray(DESCRIPTION, JsonArray()) - - if (!templateArray.contains(template)) { - templateArray.add(template) - headersArray.add(value) - descriptionArray.add("No description") - } - variableData.put(TEMPLATE, templateArray) - variableData.put(HEADERS, headersArray) - variableData.put(DESCRIPTION, descriptionArray) - } - - private fun addAdditionalData() { - val additionalData = JsonObject() - additionalData.put(ID, "None") - additionalData.put(TYPE.capitalize(), "None") - additionalData.put(IP_ADDRESS, "None") - additionalData.put(PORT, "None") - - _parsedLogJson.put(ADDITIONAL_DATA, additionalData) - } -} diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt index bb56a0fd9..4a91836f1 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt @@ -1,30 +1,22 @@ package com.cognifide.cogboard.widget.type.logviewer.logparser import org.junit.jupiter.api.Test -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.TYPE -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.DATE -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.VARIABLE_DATA -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.HEADERS -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.TEMPLATE -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.PROVIDER -import com.cognifide.cogboard.widget.type.logviewer.logparser.ParsedLog.Companion.MESSAGE +import java.lang.AssertionError -class MockLogParserStrategyTest { +class MockLogParserTest { private val sampleLog = "2021-11-06:22:40:25 *DEBUG* [FelixStartLevel] Integer lobortis. bibendum Nulla mi" - private val parser = MockLogParserStrategy() + private val parser = MockLogParser() @Test fun parseSampleLog() { - val output = parser.parseLine(sampleLog) - val variableData = output.getJsonObject(VARIABLE_DATA) - val template = variableData.getJsonArray(TEMPLATE) - val headers = variableData.getJsonArray(HEADERS) + assert(parser.variableFields == listOf("Provider", "Message")) - assert(output.getString(TYPE) == "DEBUG") - assert(output.getString(DATE) == "2021-11-06:22:40:25") - assert(template.getString(0) == PROVIDER) - assert(template.getString(1) == MESSAGE) - assert(headers.getString(0) == "FelixStartLevel") - assert(headers.getString(1) == "Integer lobortis. bibendum Nulla mi") + val output = parser.parseLine(sampleLog) ?: throw AssertionError("Parsed log should not be null") + + assert(output.type == "DEBUG") + assert(output.date == 1636238425L) + assert(output.variableData.size == 2) + assert(output.variableData[0].header == "FelixStartLevel") + assert(output.variableData[1].header == "Integer lobortis. bibendum Nulla mi") } } \ No newline at end of file From eba4b654a3d7037e32ab3e39dbaca33461719924 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 20 Nov 2021 20:12:43 +0100 Subject: [PATCH 071/226] Disable filter menu on no filters #389 --- .../types/LogViewerWidget/Toolbar/FilterPicker/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 2cb30fd8c..ed346cfa1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -39,8 +39,11 @@ const FilterPicker = ({ widgetLocalStorage }) => { return ( - Filters + + {regExpFilters.length > 0 ? `Filters` : `No filters defined`} + getFileContents(e)} /> + {filename || `Upload a File`} + getFileContents(e)} + /> - {fileInfo} + {filename && ( + + deleteFile(e)}> + + + + )} - + ); }; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index c5891e036..bf654d46b 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -119,7 +119,7 @@ const dialogFields = { SSHKeyPassphraseField: { component: PasswordInput, name: 'sshKeyPassphrase', - label: 'SSH Private Key Passphrase', + label: 'SSH private key passphrase', validator: () => string() }, PublicURL: { @@ -590,7 +590,7 @@ const dialogFields = { LogLinesField: { component: NumberInput, name: 'logLinesField', - label: 'Number of lines to return', + label: 'Maximum number of lines to return', initialValue: 1000, min: 1, step: 1, diff --git a/cogboard-webapp/src/components/widgets/dialogFields/styled.js b/cogboard-webapp/src/components/widgets/dialogFields/styled.js index 593893e84..5cbb6fdcc 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/styled.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/styled.js @@ -156,13 +156,15 @@ export const StyledMultiLineWrapper = styled.div` export const StyledHorizontalStack = styled.div` display: flex; flex-direction: row; + align-items: center; gap: 12px; `; export const StyledVerticalStack = styled.div` + margin: 16px 0 8px 0; display: flex; flex-direction: column; - gap: 12px; + gap: 6px; `; export const StyledLabel = styled.p` From 39ec14c41b74d2079f5c344fbab66fc18467420c Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Thu, 25 Nov 2021 17:09:42 +0100 Subject: [PATCH 083/226] Changes after code review --- .../types/LogViewerWidget/Toolbar/QuarantineModal/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index dc01d0fca..63c8e729b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -67,7 +67,6 @@ const QuarantineModal = ({ wid, quarantine }) => { return null; } - console.log(quarantine); const renderListItems = ( items, name, @@ -105,7 +104,7 @@ const QuarantineModal = ({ wid, quarantine }) => { disableBackdropClick={true} handleDialogClose={handleDialogClose} open={dialogOpened} - title="Manage a quarantine" + title="Quarantine management" > {renderListItems( From 8e66d46eca5c6924f9a79b80a1ef3a2892f861aa Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 26 Nov 2021 17:12:36 +0100 Subject: [PATCH 084/226] Fix filterByRegExp func #389 --- .../types/LogViewerWidget/LogList/index.js | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index ace936a91..e261ad0ff 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -16,30 +16,20 @@ export default function LogList({ widgetLocalStorage, logs, template }) { const theme = useTheme(); const filters = getFilters(widgetLocalStorage); - const filterByRegExp = (log, filters) => { - let result = true; - filters.forEach(({ regExp, checked }) => { - if (checked) { - let filterPassed = false; + const filterByRegExp = (log, filters) => + filters + .filter(f => f.checked) + .every(({ regExp }) => { const regExpObj = new RegExp(regExp); - const texts = [ - ...log.variableData.headers, - ...log.variableData.description - ]; - - texts.forEach(text => { - if (text.match(regExpObj)) { - filterPassed = true; - } + const texts = []; + // loop through log variable columns + log.variableData.forEach(({ header, description }) => { + texts.push(header); + texts.push(description); }); - if (!filterPassed) { - result = false; - } - } - }); - return result; - }; + return texts.some(text => text.match(regExpObj)); + }); const filteredLogs = logs?.filter(log => filterByRegExp(log, filters)); From 7f3ccddfd351ab4b18215d7d0e44185e29b986b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sat, 27 Nov 2021 00:44:13 +0100 Subject: [PATCH 085/226] Implemented changes from review #414 --- .../com/cognifide/cogboard/ssh/auth/SSHAuthData.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index 4e414ee45..429d44783 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -8,14 +8,14 @@ import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject import java.net.URI -class SSHAuthData(private val config: JsonObject) { +class SSHAuthData(config: JsonObject) { val user: String = config.getString(Props.USER, "") val password: String = config.getString(Props.PASSWORD, "") val token: String = config.getString(Props.TOKEN, "") val key: String = config.getString(Props.SSH_KEY, "") val host: String val port: Int - val authenticationType = fromConfigAuthenticationType() + val authenticationType = fromConfigAuthenticationType(config) init { val uriString = config.getJsonObject(Props.ENDPOINT_LOADED)?.getString(Props.URL) ?: "" @@ -24,7 +24,7 @@ class SSHAuthData(private val config: JsonObject) { port = uri.port } - private fun fromConfigAuthenticationType(): AuthenticationType { + private fun fromConfigAuthenticationType(config: JsonObject): AuthenticationType { val authTypes = config.getString(Props.AUTHENTICATION_TYPES)?.let { Json.decodeValue(it) } ?: JsonArray() @@ -41,7 +41,7 @@ class SSHAuthData(private val config: JsonObject) { fun getAuthenticationString(): String = when (authenticationType) { - BASIC -> config.getString(Props.PASSWORD) - SSH_KEY -> config.getString(Props.SSH_KEY) + BASIC -> password + SSH_KEY -> key } } From f6a4414c42695ad9d4d82638b310635bea1132ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Mon, 29 Nov 2021 18:37:00 +0100 Subject: [PATCH 086/226] Fixed build issue #414 --- .../main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index b61e5b5c6..92c3b0824 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -47,7 +47,6 @@ class SSHAuthData(config: JsonObject) { private fun prepareForSSHKeyUsage() { val fileHelper = SSHKeyFileHelper(id, key) fileHelper.saveToFile() - config.put(Props.SSH_KEY, fileHelper.path) key = fileHelper.path } From 7c4e574f1483efebfaa39ed140aeede90c4a0931 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 29 Nov 2021 22:03:13 +0100 Subject: [PATCH 087/226] Add search filter #385 --- .../types/LogViewerWidget/LogList/LogEntry.js | 23 ++++++++---- .../types/LogViewerWidget/LogList/helpers.js | 22 ++++++++++- .../types/LogViewerWidget/LogList/index.js | 25 ++++--------- .../types/LogViewerWidget/LogList/styled.js | 5 +++ .../Toolbar/SearchInput/index.js | 37 ++++++++++++++----- .../types/LogViewerWidget/Toolbar/index.js | 9 +++-- .../widgets/types/LogViewerWidget/index.js | 21 +++++++---- cogboard-webapp/src/hooks/index.js | 13 ++++++- 8 files changed, 107 insertions(+), 48 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index efb4f9bd1..21762cf74 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,16 +1,23 @@ import React, { useState } from 'react'; import { string, number, bool, shape, oneOfType, arrayOf } from 'prop-types'; +import { getGridTemplate } from './helpers'; import { AccordionSummary, AccordionDetails } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { GridSchema, Text, CustomAccordion, - VariableGridSchema + VariableGridSchema, + HighlightedAccordion } from './styled'; -import getGridTemplate from './helpers'; -export default function LogEntry({ type, date, variableData, template }) { +export default function LogEntry({ + type, + date, + variableData, + template, + highlight +}) { const [expanded, setExpanded] = useState(false); const VariablePart = ({ description }) => { @@ -29,8 +36,10 @@ export default function LogEntry({ type, date, variableData, template }) { ); }; + const SelectedAccordion = highlight ? HighlightedAccordion : CustomAccordion; + return ( - + setExpanded(!expanded)} expandIcon={expanded && } @@ -46,7 +55,7 @@ export default function LogEntry({ type, date, variableData, template }) { - + ); } @@ -55,8 +64,8 @@ LogEntry.propTypes = { date: string.isRequired, variableData: arrayOf( shape({ - header: arrayOf(oneOfType([string, number, bool])).isRequired, - description: arrayOf(oneOfType([string, number, bool])).isRequired + header: oneOfType([string, number, bool]).isRequired, + description: oneOfType([string, number, bool]).isRequired }) ) }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 25f6c24df..35f03f195 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -1,8 +1,26 @@ -const getGridTemplate = columnNames => { +export const getGridTemplate = columnNames => { const widths = columnNames.map(name => name.toLowerCase() === 'message' ? '3fr ' : '1fr ' ); return widths.reduce((acc, current) => acc + current, ''); }; -export default getGridTemplate; +const getLogTexts = log => { + const texts = []; + // loop through log variable columns + log.variableData.forEach(({ header, description }) => { + texts.push(header); + texts.push(description); + }); + return texts; +}; + +export const shouldHighlight = (log, search) => + search && getLogTexts(log).some(text => text.match(new RegExp(search, 'i'))); + +export const filterByRegExp = (log, filters) => + filters + .filter(f => f.checked) + .every(({ regExp }) => + getLogTexts(log).some(text => text.match(new RegExp(regExp))) + ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index e261ad0ff..1cc15e325 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { useTheme } from '@material-ui/core'; +import { getGridTemplate, filterByRegExp, shouldHighlight } from './helpers'; import LogEntry from './LogEntry'; import { Container, @@ -9,28 +10,17 @@ import { LogsWrapper, VariableGridSchema } from './styled'; -import getGridTemplate from './helpers'; import { getFilters } from '../Toolbar/FilterPicker/helpers'; -export default function LogList({ widgetLocalStorage, logs, template }) { +export default function LogList({ + widgetLocalStorage, + logs, + template, + search +}) { const theme = useTheme(); const filters = getFilters(widgetLocalStorage); - const filterByRegExp = (log, filters) => - filters - .filter(f => f.checked) - .every(({ regExp }) => { - const regExpObj = new RegExp(regExp); - const texts = []; - // loop through log variable columns - log.variableData.forEach(({ header, description }) => { - texts.push(header); - texts.push(description); - }); - - return texts.some(text => text.match(regExpObj)); - }); - const filteredLogs = logs?.filter(log => filterByRegExp(log, filters)); const VariableLogListHeader = () => ( @@ -60,6 +50,7 @@ export default function LogList({ widgetLocalStorage, logs, template }) { date={log.date} variableData={log.variableData} template={template} + highlight={shouldHighlight(log, search)} /> ))}
diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 3a1b9db7d..8dd99251d 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -101,3 +101,8 @@ export const CustomAccordion = styled(Accordion)` background-color: ${COLORS.DARK_SHADE}; } `; + +export const HighlightedAccordion = styled(CustomAccordion)` + border: 1px solid ${COLORS.YELLOW}; + margin: 2px 0 3px; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index d420317a2..4df851270 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -1,16 +1,35 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { useDebounce } from '../../../../../../hooks'; import { TextField } from '@material-ui/core'; import { Wrapper, CustomIconButton } from './styled'; import SearchIcon from '@material-ui/icons/Search'; -const SearchInput = () => ( - - - - - - -); +const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { + const [searchBoxValue, setSearchBoxValue] = useState(''); + + const valueToSearch = useDebounce(searchBoxValue, debounce); + const enoughLetters = valueToSearch.length >= minLetters; + useEffect(() => setSearchFilter(enoughLetters ? valueToSearch : ''), [ + valueToSearch, + setSearchFilter, + enoughLetters + ]); + + const handleChange = e => setSearchBoxValue(e.target.value); + + return ( + + + + + + + ); +}; export default SearchInput; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 31eed102a..70cfdd582 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -9,13 +9,16 @@ import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; import QuarantineModal from './QuarantineModal'; - -const Toolbar = ({ wid, quarantine, widgetLocalStorage }) => { +const Toolbar = ({ wid, quarantine, widgetLocalStorage, setSearchFilter }) => { const theme = useTheme(); return ( - + diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 9c623d4a8..6d575957c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import { number, string } from 'prop-types'; import { useLocalStorage } from '../../../../hooks'; @@ -20,21 +20,26 @@ const LogViewerWidget = ({ id }) => { set: setWidgetLocalStorage }; + const [searchFilter, setSearchFilter] = useState(''); + const logs = widgetData.content?.logs; const template = widgetData.content?.variableFields; const quarantine = widgetData.content?.quarantine || []; + return ( - {logs && ( - )} diff --git a/cogboard-webapp/src/hooks/index.js b/cogboard-webapp/src/hooks/index.js index a03e834a6..d5f0b6d69 100644 --- a/cogboard-webapp/src/hooks/index.js +++ b/cogboard-webapp/src/hooks/index.js @@ -137,7 +137,7 @@ export function useEventListener(eventName, handler, element = window) { }, [eventName, element]); } -export function useLocalStorage(key) { +export const useLocalStorage = key => { const localStorage = window.localStorage.getItem(key); const [data, setStoredValue] = useState( localStorage ? JSON.parse(localStorage) : null @@ -148,4 +148,13 @@ export function useLocalStorage(key) { setStoredValue(data); }; return [data, setData]; -} +}; + +export const useDebounce = (value, delay) => { + const [debouncedValue, setDebouncedValue] = useState(value); + useEffect(() => { + const timeoutRef = setTimeout(() => setDebouncedValue(value), delay); + return () => clearTimeout(timeoutRef); + }, [value, delay]); + return debouncedValue; +}; From d7933761ba51c2e79611b0ac21ac41dcde9e2d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 30 Nov 2021 22:48:13 +0100 Subject: [PATCH 088/226] Add quarantine rules --- .../cogboard/logStorage/LogStorage.kt | 35 ++++++++++++---- .../cogboard/logStorage/QuarantineRule.kt | 37 ++++++++++++++++ .../cogboard/ssh/auth/SSHAuthData.kt | 2 +- .../widget/type/logviewer/LogViewerWidget.kt | 42 +++++++++++++++++-- .../Toolbar/QuarantineModal/index.js | 10 ++--- .../widgets/types/LogViewerWidget/index.js | 16 +++---- 6 files changed, 117 insertions(+), 25 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/QuarantineRule.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index fb6bb7154..d1fc9a204 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -22,13 +22,15 @@ import io.vertx.core.logging.LoggerFactory import org.bson.Document import main.kotlin.com.cognifide.cogboard.logStorage.Log import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration +import main.kotlin.com.cognifide.cogboard.logStorage.QuarantineRule import java.net.URI import java.time.Instant class LogStorage( private val config: LogStorageConfiguration, private val connection: ConnectionStrategy, - private val parserStrategy: LogParserStrategy + private val parserStrategy: LogParserStrategy, + var rules: List = emptyList() ) : AbstractVerticle() { override fun start() { @@ -122,12 +124,27 @@ class LogStorage( } } + /** Filters the logs in place. */ + private fun filter(logs: MutableList) { + if (rules.isEmpty()) { return } + val regexes = rules.map { it.regex } + logs.retainAll { log -> + log.variableData.any { variable -> + regexes.any { it.containsMatchIn(variable.header) } + } + } + } + /** Downloads new logs and inserts the to the database. Returns the number of inserted logs. */ private fun downloadInsertLogs(seq: Long, skipFirstLines: Long? = null): Long { var sequence = seq - val logs = connection + var logs = connection .getLogs(skipFirstLines) .mapNotNull { parserStrategy.parseLine(it) } + .toMutableList() + + // Filter the logs by quarantine rules + filter(logs) logs.forEach { it.seq = sequence sequence += 1 @@ -184,13 +201,15 @@ class LogStorage( } /** Updates the logs and sends them to the widget. */ - fun updateLogs() { - // Download new logs - downloadLogs() + fun updateLogs(fetchNewLogs: Boolean) { + if (fetchNewLogs) { + // Download new logs + downloadLogs() - // Delete unnecessary logs - deleteOldLogs() - deleteSpaceConsumingLogs() + // Delete unnecessary logs + deleteOldLogs() + deleteSpaceConsumingLogs() + } // Fetch the logs from the database and send them back val response = prepareResponse() diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/QuarantineRule.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/QuarantineRule.kt new file mode 100644 index 000000000..b34c6bf54 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/QuarantineRule.kt @@ -0,0 +1,37 @@ +package main.kotlin.com.cognifide.cogboard.logStorage + +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject + +data class QuarantineRule( + val label: String, + val reasonField: String, + val regex: Regex, + val enabled: Boolean +) { + companion object { + private const val LABEL = "label" + private const val REASON = "reasonField" + private const val REGEX = "regExp" + private const val ENABLED = "checked" + + fun from(json: JsonObject): QuarantineRule? { + return try { + QuarantineRule( + json.getString(LABEL)!!, + json.getString(REASON)!!, + json.getString(REGEX)!!.toRegex(), + json.getBoolean(ENABLED)!! + ) + } catch (_: NullPointerException) { + null + } + } + + fun from(array: JsonArray): List = + array + .mapNotNull { it } + .mapNotNull { it as? JsonObject } + .mapNotNull { from(it) } + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index d765acfbd..9550c5bfd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -24,7 +24,7 @@ class SSHAuthData(private val config: JsonObject) { val uri = URI.create(uriString) host = uri.host port = uri.port - if(authenticationType == SSH_KEY) { + if (authenticationType == SSH_KEY) { prepareForSSHKeyUsage() } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index c43f23215..077dba525 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -3,6 +3,7 @@ package com.cognifide.cogboard.widget.type.logviewer import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.logStorage.LogStorage +import com.cognifide.cogboard.storage.ContentRepository import com.cognifide.cogboard.widget.BaseWidget import com.cognifide.cogboard.widget.Widget import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy @@ -11,14 +12,17 @@ import com.cognifide.cogboard.widget.connectionStrategy.UnknownConnectionTypeExc import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategyFactory import io.vertx.core.Vertx import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration +import main.kotlin.com.cognifide.cogboard.logStorage.QuarantineRule class LogViewerWidget( vertx: Vertx, config: JsonObject, serv: BoardsConfigService ) : BaseWidget(vertx, config, serv) { + private val contentRepository: ContentRepository = ContentRepository.DEFAULT private val address = config.endpointProp(Props.URL) private var consumer: MessageConsumer? = null private val connectionStrategy: ConnectionStrategy? = determineConnectionStrategy() @@ -30,12 +34,22 @@ class LogViewerWidget( ) } + init { + // Create a handler for updating the state of the widget. + createDynamicChangeSubscriber()?.handler { newState -> + newState?.body()?.let { + contentRepository.save(id, it) + updateWidget(false) + } + } + } + override fun start(): Widget { vertx.deployVerticle(logStorage) consumer = vertx.eventBus() .consumer(eventBusAddress) - .handler { message -> - message?.body()?.let { send(it) } + .handler { logs -> + logs?.body()?.let { sendResponse(it) } } return super.start() } @@ -48,13 +62,33 @@ class LogViewerWidget( } override fun updateState() { + updateWidget(true) + } + + /** Updates the contents of the widget (optionally fetching new logs when [fetchNewLogs] is true). */ + private fun updateWidget(fetchNewLogs: Boolean) { if (address.isNotBlank()) { - logStorage?.updateLogs() + logStorage?.rules = rules + logStorage?.updateLogs(fetchNewLogs) } else { sendConfigurationError("Endpoint URL is blank") } } + /** Sends the updated state to the client. */ + private fun sendResponse(logs: JsonObject) { + val rules = contentRepository.get(id).getJsonArray(QUARANTINE_RULES) ?: JsonArray() + logs.put(QUARANTINE_RULES, rules) + send(logs) + } + + /** Gets the quarantine rules from the */ + private val rules: List = + contentRepository + .get(id) + .getJsonArray(QUARANTINE_RULES) + ?.let { QuarantineRule.from(it) } ?: emptyList() + private fun buildConfiguration(config: JsonObject): LogStorageConfiguration { return LogStorageConfiguration( config.getString(Props.ID) ?: DEFAULT_ID, @@ -84,5 +118,7 @@ class LogViewerWidget( private const val DEFAULT_LOG_LINES = 100L private const val DEFAULT_LOG_FILE_SIZE = 50L private const val DEFAULT_LOG_EXPIRATION_DAYS = 5L + + private const val QUARANTINE_RULES = "quarantineRules" } } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index 63c8e729b..06a270d59 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -30,14 +30,14 @@ const QuarantineModal = ({ wid, quarantine }) => { const addFilter = values => { postWidgetContentUpdate({ - wid, + id: wid, quarantineRules: [...quarantine, { id: v4(), checked: true, ...values }] }); }; const editFilter = ({ id, values }) => { postWidgetContentUpdate({ - wid, + id: wid, quarantineRules: quarantine.map(filter => { if (filter.id === id) { return { id, ...values }; @@ -49,7 +49,7 @@ const QuarantineModal = ({ wid, quarantine }) => { const handleSwitchChange = id => { postWidgetContentUpdate({ - wid, + id: wid, quarantineRules: quarantine.map(filter => filter.id === id ? { ...filter, checked: !filter.checked } : filter ) @@ -58,7 +58,7 @@ const QuarantineModal = ({ wid, quarantine }) => { const deleteAction = id => { postWidgetContentUpdate({ - wid, + id: wid, quarantineRules: quarantine.filter(quarantine => quarantine.id !== id) }); }; @@ -88,7 +88,7 @@ const QuarantineModal = ({ wid, quarantine }) => { /> handleSwitchChange(id)} color="secondary" > diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 9c623d4a8..e7ad9a276 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -22,19 +22,19 @@ const LogViewerWidget = ({ id }) => { const logs = widgetData.content?.logs; const template = widgetData.content?.variableFields; - const quarantine = widgetData.content?.quarantine || []; + const quarantine = widgetData.content?.quarantineRules || []; return ( - {logs && ( - )} From 5051f19b3901794fbcabebbae2d78b9538e7362e Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 1 Dec 2021 16:30:30 +0100 Subject: [PATCH 089/226] Add searchbar clear button #385 --- .../LogViewerWidget/Toolbar/SearchInput/index.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index 4df851270..8b0e0f69e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -4,6 +4,7 @@ import { useDebounce } from '../../../../../../hooks'; import { TextField } from '@material-ui/core'; import { Wrapper, CustomIconButton } from './styled'; import SearchIcon from '@material-ui/icons/Search'; +import CloseIcon from '@material-ui/icons/Close'; const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { const [searchBoxValue, setSearchBoxValue] = useState(''); @@ -17,6 +18,10 @@ const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { ]); const handleChange = e => setSearchBoxValue(e.target.value); + const clearSearch = () => { + setSearchBoxValue(''); + setSearchFilter(''); + }; return ( @@ -25,8 +30,13 @@ const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { value={searchBoxValue} onChange={handleChange} /> - - + + {enoughLetters ? : } ); From 8f29394a2e733138c91ca6157a9f2567aceee102 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 1 Dec 2021 17:00:28 +0100 Subject: [PATCH 090/226] Add prop types #385 --- .../Toolbar/SearchInput/index.js | 19 +++++++++++++------ .../Toolbar/SearchInput/styled.js | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index 8b0e0f69e..45d7f2bb7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { func, number } from 'prop-types'; import { useDebounce } from '../../../../../../hooks'; import { TextField } from '@material-ui/core'; @@ -30,16 +31,22 @@ const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { value={searchBoxValue} onChange={handleChange} /> - + {enoughLetters ? : } ); }; +SearchInput.propTypes = { + setSearchFilter: func.isRequired, + debounce: number, + minLetters: number +}; + +SearchInput.defaultProps = { + debounce: 500, + minLetters: 3 +}; + export default SearchInput; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js index 9f3e335d7..94214039e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js @@ -12,3 +12,7 @@ export const CustomIconButton = styled(IconButton)` bottom: 0; right: 0; `; +CustomIconButton.defaultProps = { + variant: 'outlined', + size: 'small' +}; From c2671a7f4d83015333645cce33b6cff7d5306884 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 1 Dec 2021 20:14:01 +0100 Subject: [PATCH 091/226] Add datespan to localstorage #383 --- .../Toolbar/DateRangePicker/CustomPicker.js | 18 +++++++ .../Toolbar/DateRangePicker/helpers.js | 10 ++++ .../Toolbar/DateRangePicker/index.js | 52 +++++++++++++------ .../Toolbar/DateRangePicker/styled.js | 16 ++++++ .../types/LogViewerWidget/Toolbar/index.js | 3 +- 5 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/helpers.js create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js new file mode 100644 index 000000000..0de41e1ad --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js @@ -0,0 +1,18 @@ +import React from 'react'; + +import { DateTimePicker } from '@material-ui/pickers'; +import CloseIcon from '@material-ui/icons/Close'; +import { PickerWrapper, StyledIconButton } from './styled'; + +const CustomPicker = ({ value, onChange, ...props }) => ( + + + {value && ( + onChange(null)}> + + + )} + +); + +export default CustomPicker; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/helpers.js new file mode 100644 index 000000000..554e8e86f --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/helpers.js @@ -0,0 +1,10 @@ +export const saveDateSpan = ( + { get: localStorage, set: setLocalStorage }, + dateSpan +) => { + const newWidgetData = { ...localStorage(), dateSpan: dateSpan }; + setLocalStorage(newWidgetData); +}; + +export const getDateSpan = widgetLocalStorage => + widgetLocalStorage.get()?.dateSpan || { begin: null, end: null }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js index 2b73ad2a0..bc3193d0a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js @@ -1,23 +1,41 @@ import React from 'react'; import MomentUtils from '@date-io/moment'; -import { MuiPickersUtilsProvider, DateTimePicker } from '@material-ui/pickers'; +import { getDateSpan, saveDateSpan } from './helpers'; + import ToolbarGroup from '../ToolbarGroup'; +import { MuiPickersUtilsProvider } from '@material-ui/pickers'; +import CustomPicker from './CustomPicker'; + +const format = 'hh:mm DD/MM/YY'; + +const DateRangePicker = ({ widgetLocalStorage }) => { + const { begin, end } = getDateSpan(widgetLocalStorage); + + const handleBeginChange = date => + saveDateSpan(widgetLocalStorage, { begin: date, end }); + const handleEndChange = date => + saveDateSpan(widgetLocalStorage, { begin, end: date }); -const DateRangePicker = () => ( - - - console.log('TODO')} - /> - console.log('TODO')} - /> - - -); + return ( + + + + + + + ); +}; export default DateRangePicker; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js new file mode 100644 index 000000000..829fe0c4c --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js @@ -0,0 +1,16 @@ +import styled from '@emotion/styled/macro'; +import { IconButton } from '@material-ui/core'; + +export const PickerWrapper = styled.div` + position: relative; +`; + +export const StyledIconButton = styled(IconButton)` + position: absolute; + right: 0; + bottom: 2px; +`; +StyledIconButton.defaultProps = { + size: 'small', + variant: 'outlined' +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 31eed102a..b49272c24 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -9,7 +9,6 @@ import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; import QuarantineModal from './QuarantineModal'; - const Toolbar = ({ wid, quarantine, widgetLocalStorage }) => { const theme = useTheme(); return ( @@ -20,7 +19,7 @@ const Toolbar = ({ wid, quarantine, widgetLocalStorage }) => { - + - + Clear logs From 99c4ddf03d603f96d57ae5cec441fa9d30ef85fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 5 Dec 2021 02:32:15 +0100 Subject: [PATCH 096/226] Add logs limit --- .../main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index fabb92139..b3923616b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -206,6 +206,7 @@ class LogStorage( private val logs: List get() = logsCollection ?.find() + ?.limit(config.logLines.toInt()) ?.sort(descending(Log.SEQ)) ?.mapNotNull { it } ?.mapNotNull { Log.from(it) } From 262e288b42813bf6847bb2e8ab94e2b45ae0760a Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Mon, 6 Dec 2021 14:54:16 +0100 Subject: [PATCH 097/226] Added logic for filtering by log level --- .../types/LogViewerWidget/LogList/index.js | 19 +++++++++++++++++-- .../Toolbar/FilterPicker/helpers.js | 11 +++++++++++ .../Toolbar/FilterPicker/index.js | 10 ++++++---- .../types/LogViewerWidget/logLevels.js | 10 +++++----- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index e261ad0ff..b4ba51817 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -10,11 +10,13 @@ import { VariableGridSchema } from './styled'; import getGridTemplate from './helpers'; -import { getFilters } from '../Toolbar/FilterPicker/helpers'; +import { getFilters, getLevel } from '../Toolbar/FilterPicker/helpers'; +import logLevels from '../logLevels'; export default function LogList({ widgetLocalStorage, logs, template }) { const theme = useTheme(); const filters = getFilters(widgetLocalStorage); + const level = getLevel(widgetLocalStorage); const filterByRegExp = (log, filters) => filters @@ -31,7 +33,20 @@ export default function LogList({ widgetLocalStorage, logs, template }) { return texts.some(text => text.match(regExpObj)); }); - const filteredLogs = logs?.filter(log => filterByRegExp(log, filters)); + const filterByLevel = (log, level) => { + const lowestLevel = logLevels.find( + elem => elem.value.toLowerCase() === level.toLowerCase() + ); + const logLevel = logLevels.find( + elem => elem.value.toLowerCase() === log.type.toLowerCase() + ); + return logLevel.level >= lowestLevel.level; + }; + + const prefilteredLogs = logs?.filter(log => filterByRegExp(log, filters)); + const filteredLogs = prefilteredLogs?.filter(log => + filterByLevel(log, level) + ); const VariableLogListHeader = () => ( diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js index aab41bb80..b9e186871 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/helpers.js @@ -8,3 +8,14 @@ export const saveFilters = ( export const getFilters = widgetLocalStorage => widgetLocalStorage.get()?.regExpFilters || []; + +export const saveLevel = ( + { get: localStorage, set: setLocalStorage }, + level +) => { + const newWidgetData = { ...localStorage(), logsLevel: level }; + setLocalStorage(newWidgetData); +}; + +export const getLevel = widgetLocalStorage => + widgetLocalStorage.get()?.logsLevel || 'info'; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index a344bebf7..f0a50559d 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { getFilters, saveFilters } from './helpers'; +import React from 'react'; +import { getFilters, getLevel, saveFilters, saveLevel } from './helpers'; import logLevels from '../../logLevels'; @@ -17,7 +17,7 @@ import AdvancedFiltersMenu from './AdvancedFiltersMenu'; const FilterPicker = ({ widgetLocalStorage }) => { const regExpFilters = getFilters(widgetLocalStorage); - const [logLevel, setLogLevel] = useState('info'); + const logLevel = getLevel(widgetLocalStorage); const handleSelection = selectedList => saveFilters( @@ -36,6 +36,8 @@ const FilterPicker = ({ widgetLocalStorage }) => { ) ); + const handleLevelSelection = level => saveLevel(widgetLocalStorage, level); + return ( @@ -86,7 +88,7 @@ const FilterPicker = ({ widgetLocalStorage }) => { label="Log level" style={{ width: '100px' }} value={logLevel} - onChange={e => setLogLevel(e.target.value)} + onChange={e => handleLevelSelection(e.target.value)} > {logLevels.map((level, index) => ( diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js index c5ff784bf..1c6f2682a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js @@ -1,11 +1,11 @@ import { COLORS } from '../../../../constants'; const logLevels = [ - { value: 'debug', color: COLORS.WHITE }, - { value: 'info', color: COLORS.WHITE }, - { value: 'warning', color: COLORS.YELLOW }, - { value: 'error', color: COLORS.RED }, - { value: 'success', color: COLORS.GREEN } + { value: 'debug', color: COLORS.WHITE, level: 0 }, + { value: 'info', color: COLORS.WHITE, level: 1 }, + { value: 'warning', color: COLORS.YELLOW, level: 2 }, + { value: 'error', color: COLORS.RED, level: 3 }, + { value: 'success', color: COLORS.GREEN, level: 4 } ]; export default logLevels; From 3287197407539b0a545126c7053d42610769a2f3 Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 7 Dec 2021 19:41:50 +0100 Subject: [PATCH 098/226] Add text highlight #385 --- .../types/LogViewerWidget/LogList/LogEntry.js | 26 +++++++++++-------- .../types/LogViewerWidget/LogList/helpers.js | 17 ++++++++++-- .../types/LogViewerWidget/LogList/index.js | 5 ++-- .../types/LogViewerWidget/LogList/styled.js | 23 +++++++++++----- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index 21762cf74..f6590307c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { string, number, bool, shape, oneOfType, arrayOf } from 'prop-types'; -import { getGridTemplate } from './helpers'; +import { getGridTemplate, highlightText } from './helpers'; import { AccordionSummary, AccordionDetails } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { @@ -8,7 +8,8 @@ import { Text, CustomAccordion, VariableGridSchema, - HighlightedAccordion + HighlightedText, + HighlightMark } from './styled'; export default function LogEntry({ @@ -16,6 +17,7 @@ export default function LogEntry({ date, variableData, template, + search, highlight }) { const [expanded, setExpanded] = useState(false); @@ -27,23 +29,25 @@ export default function LogEntry({ template={variableFieldsTemplate} skipColumns={description} > - {variableData.map((entry, index) => ( - - {description ? entry.description : entry.header} - - ))} + {variableData.map((entry, index) => { + const entryText = description ? entry.description : entry.header; + return ( + + {highlightText(entryText, search, HighlightedText)} + + ); + })} ); }; - const SelectedAccordion = highlight ? HighlightedAccordion : CustomAccordion; - return ( - + setExpanded(!expanded)} expandIcon={expanded && } > + {highlight && } {type?.toUpperCase()} {date} @@ -55,7 +59,7 @@ export default function LogEntry({ - + ); } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 35f03f195..341a07ba5 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -15,8 +15,21 @@ const getLogTexts = log => { return texts; }; -export const shouldHighlight = (log, search) => - search && getLogTexts(log).some(text => text.match(new RegExp(search, 'i'))); +export const isLogHighlighted = (log, search) => + search && getLogTexts(log).some(text => text.match(search)); + +export const highlightText = (text, search, Component) => + search + ? text + .split(new RegExp(`(${search})`, 'gi')) + .map((part, index) => + part.toLowerCase() === search.toLowerCase() ? ( + {part} + ) : ( + {part} + ) + ) + : text; export const filterByRegExp = (log, filters) => filters diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 1cc15e325..e0e759f9e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { useTheme } from '@material-ui/core'; -import { getGridTemplate, filterByRegExp, shouldHighlight } from './helpers'; +import { getGridTemplate, filterByRegExp, isLogHighlighted } from './helpers'; import LogEntry from './LogEntry'; import { Container, @@ -50,7 +50,8 @@ export default function LogList({ date={log.date} variableData={log.variableData} template={template} - highlight={shouldHighlight(log, search)} + search={search} + highlight={isLogHighlighted(log, search)} /> ))} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 8dd99251d..297b8473b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -37,11 +37,11 @@ export const ColumnTitle = styled(Typography)` font-size: 0.85rem; `; -export const Text = styled(Typography)(props => { +export const Text = styled(Typography)(({ type }) => { let logTypeStyles = ``; - if (props.type) { + if (type) { const logLevel = logLevels.find( - level => level.value === props.type?.toLowerCase() + level => level.value === type?.toLowerCase() ); logTypeStyles = ` font-weight: 500; @@ -57,6 +57,11 @@ export const Text = styled(Typography)(props => { `; }); +export const HighlightedText = styled.span` + color: ${COLORS.BLACK}; + background-color: ${COLORS.YELLOW}; +`; + export const LogsWrapper = styled.div` padding: 6px 0; overflow-y: auto; @@ -68,6 +73,7 @@ export const CustomAccordion = styled(Accordion)` &.MuiPaper-root { background-color: ${COLORS.LIGHT_SHADE}; + overflow: hidden; } &.Mui-expanded { margin: 0.5em 0; @@ -102,7 +108,12 @@ export const CustomAccordion = styled(Accordion)` } `; -export const HighlightedAccordion = styled(CustomAccordion)` - border: 1px solid ${COLORS.YELLOW}; - margin: 2px 0 3px; +export const HighlightMark = styled.div` + position: absolute; + left: -0.6rem; + top: -0.7rem; + height: 1.7rem; + width: 1rem; + transform: rotate(45deg); + background-color: ${COLORS.YELLOW}; `; From 04c01672f6f38fd112479b35066c37ccf7447ebd Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 7 Dec 2021 20:16:17 +0100 Subject: [PATCH 099/226] Fix case sensitivity #385 --- .../components/widgets/types/LogViewerWidget/LogList/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 341a07ba5..a673d3e56 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -16,7 +16,7 @@ const getLogTexts = log => { }; export const isLogHighlighted = (log, search) => - search && getLogTexts(log).some(text => text.match(search)); + search && getLogTexts(log).some(text => text.match(new RegExp(search, 'i'))); export const highlightText = (text, search, Component) => search From efd4379d773f35cfed5f172b5f8e1d00a04f349a Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Wed, 8 Dec 2021 14:56:15 +0100 Subject: [PATCH 100/226] filterByLogLevels moved to helpers --- .../types/LogViewerWidget/LogList/LogEntry.js | 2 +- .../types/LogViewerWidget/LogList/helpers.js | 14 +++++++++++-- .../types/LogViewerWidget/LogList/index.js | 20 ++++--------------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index efb4f9bd1..830dbdf9a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -8,7 +8,7 @@ import { CustomAccordion, VariableGridSchema } from './styled'; -import getGridTemplate from './helpers'; +import { getGridTemplate } from './helpers'; export default function LogEntry({ type, date, variableData, template }) { const [expanded, setExpanded] = useState(false); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 25f6c24df..4fe889212 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -1,8 +1,18 @@ -const getGridTemplate = columnNames => { +import logLevels from '../logLevels'; + +export const getGridTemplate = columnNames => { const widths = columnNames.map(name => name.toLowerCase() === 'message' ? '3fr ' : '1fr ' ); return widths.reduce((acc, current) => acc + current, ''); }; -export default getGridTemplate; +export const filterByLevel = (log, level) => { + const lowestLevel = logLevels.find( + elem => elem.value.toLowerCase() === level.toLowerCase() + ); + const logLevel = logLevels.find( + elem => elem.value.toLowerCase() === log.type.toLowerCase() + ); + return logLevel.level >= lowestLevel.level; +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index b4ba51817..f446522bd 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -9,9 +9,8 @@ import { LogsWrapper, VariableGridSchema } from './styled'; -import getGridTemplate from './helpers'; +import { getGridTemplate, filterByLevel } from './helpers'; import { getFilters, getLevel } from '../Toolbar/FilterPicker/helpers'; -import logLevels from '../logLevels'; export default function LogList({ widgetLocalStorage, logs, template }) { const theme = useTheme(); @@ -33,20 +32,9 @@ export default function LogList({ widgetLocalStorage, logs, template }) { return texts.some(text => text.match(regExpObj)); }); - const filterByLevel = (log, level) => { - const lowestLevel = logLevels.find( - elem => elem.value.toLowerCase() === level.toLowerCase() - ); - const logLevel = logLevels.find( - elem => elem.value.toLowerCase() === log.type.toLowerCase() - ); - return logLevel.level >= lowestLevel.level; - }; - - const prefilteredLogs = logs?.filter(log => filterByRegExp(log, filters)); - const filteredLogs = prefilteredLogs?.filter(log => - filterByLevel(log, level) - ); + const filteredLogs = logs + ?.filter(log => filterByRegExp(log, filters)) + .filter(log => filterByLevel(log, level)); const VariableLogListHeader = () => ( From 03d6f9fc90dd9f727c03b1fafae1b661441b1292 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 8 Dec 2021 16:01:32 +0100 Subject: [PATCH 101/226] Add list virtualizer #436 --- cogboard-webapp/package-lock.json | 22 +++++++++++ cogboard-webapp/package.json | 1 + .../types/LogViewerWidget/LogList/index.js | 38 ++++++++++++------- .../types/LogViewerWidget/LogList/styled.js | 12 ++++-- .../widgets/types/LogViewerWidget/index.js | 3 +- 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/cogboard-webapp/package-lock.json b/cogboard-webapp/package-lock.json index 1ec0e5862..716308859 100644 --- a/cogboard-webapp/package-lock.json +++ b/cogboard-webapp/package-lock.json @@ -2700,6 +2700,19 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@virtuoso.dev/react-urx": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@virtuoso.dev/react-urx/-/react-urx-0.2.12.tgz", + "integrity": "sha512-Lcrrmq/UztM+rgepAdThdIk8dL3LEi9o2NTkL6ZLKPrTGjr5tSmsauD30/O8yu7Q0ncDnptmMR3OObdvMGuEKQ==", + "requires": { + "@virtuoso.dev/urx": "^0.2.12" + } + }, + "@virtuoso.dev/urx": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@virtuoso.dev/urx/-/urx-0.2.12.tgz", + "integrity": "sha512-Q9nlRqYb5Uq4Ynu8cWPSJ7LxpuZsI+MZ09IJlDAAgwuNgfWRArpVRP0VN0coYgUo2fKMjhmV69MTqaUbIBhu/g==" + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -13742,6 +13755,15 @@ "prop-types": "^15.6.2" } }, + "react-virtuoso": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-2.3.1.tgz", + "integrity": "sha512-Y5qsh5xaGvMAN7S2LOm0n5Hg5gl76GEFSNzD/LNSR6Bm8UFncmEwIKEqp7lJMyM9aLrFV67Ap4rA0JL63Eb/uw==", + "requires": { + "@virtuoso.dev/react-urx": "^0.2.12", + "@virtuoso.dev/urx": "^0.2.12" + } + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", diff --git a/cogboard-webapp/package.json b/cogboard-webapp/package.json index 8e5007ffd..3ff57e260 100644 --- a/cogboard-webapp/package.json +++ b/cogboard-webapp/package.json @@ -27,6 +27,7 @@ "react-iframe": "latest", "react-redux": "^7.2.0", "react-scripts": "^4.0.3", + "react-virtuoso": "^2.3.1", "redux": "^4.0.5", "redux-thunk": "^2.3.0", "reselect": "^4.0.0", diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index beb216e95..52f5e23ff 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -1,5 +1,4 @@ import React from 'react'; -import { useTheme } from '@material-ui/core'; import { getGridTemplate, filterByRegExp, @@ -8,6 +7,7 @@ import { } from './helpers'; import { getFilters } from '../Toolbar/FilterPicker/helpers'; import { getDateSpan } from '../Toolbar/DateRangePicker/helpers'; +import { useTheme } from '@material-ui/core'; import LogEntry from './LogEntry'; import { Container, @@ -15,6 +15,7 @@ import { GridSchema, ColumnTitle, LogsWrapper, + StyledVirtuoso, VariableGridSchema } from './styled'; @@ -30,7 +31,8 @@ export default function LogList({ const filteredLogs = logs ?.filter(log => filterByRegExp(log, filters)) - .filter(log => filterByDateSpan(log, dateSpan)); + .filter(log => filterByDateSpan(log, dateSpan)) + .reverse(); // maybe logs can be sent in correct order const VariableLogListHeader = () => ( @@ -51,18 +53,26 @@ export default function LogList({ - {filteredLogs?.map((log, index) => ( - - ))} + { + const log = filteredLogs[index]; + return ( + + ); + }} + />
); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 297b8473b..72a7f09c9 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -1,10 +1,11 @@ import styled from '@emotion/styled/macro'; +import { Virtuoso } from 'react-virtuoso'; import { COLORS } from '../../../../../constants'; import { Typography, Accordion } from '@material-ui/core'; import logLevels from '../logLevels'; export const Container = styled.div` - max-height: 100%; + height: 100%; display: grid; padding-top: 6em; grid-template-rows: auto 1fr; @@ -63,8 +64,11 @@ export const HighlightedText = styled.span` `; export const LogsWrapper = styled.div` - padding: 6px 0; - overflow-y: auto; + padding-top: 6px; + height: 100%; +`; + +export const StyledVirtuoso = styled(Virtuoso)` height: 100%; `; @@ -76,7 +80,7 @@ export const CustomAccordion = styled(Accordion)` overflow: hidden; } &.Mui-expanded { - margin: 0.5em 0; + margin: 0.5em 0 !important; } .MuiAccordionSummary-root { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 07426e647..54d9763d8 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -25,14 +25,13 @@ const LogViewerWidget = ({ id }) => { const logs = widgetData.content?.logs; const template = widgetData.content?.variableFields; const quarantine = widgetData.content?.quarantineRules || []; - + return ( {logs && ( From 9c232d30f8f62d23fd29018147a2ff5dda7eba4d Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 8 Dec 2021 19:59:28 +0100 Subject: [PATCH 102/226] Setup following logs #394 --- .../types/LogViewerWidget/LogList/index.js | 36 ++++++++++++++++--- .../types/LogViewerWidget/Toolbar/index.js | 17 +++++++-- .../widgets/types/LogViewerWidget/index.js | 5 +++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 52f5e23ff..999273be4 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import { getGridTemplate, filterByRegExp, @@ -23,9 +23,14 @@ export default function LogList({ widgetLocalStorage, logs, template, - search + search, + shouldFollowLogs, + handleFollowChange }) { const theme = useTheme(); + const scrollerRef = useRef(null); + const [scroll, setScroll] = useState(0); + const filters = getFilters(widgetLocalStorage); const dateSpan = getDateSpan(widgetLocalStorage); @@ -42,6 +47,26 @@ export default function LogList({
); + useEffect(() => { + if (shouldFollowLogs) { + console.log(scrollerRef.current.scrollHeight); + console.log(scrollerRef.current.scrollTop); + scrollerRef.current.scrollTo({ + top: scrollerRef.current.scrollHeight, + behavior: 'smooth' + }); + } + setScroll(scrollerRef.current.scrollTop); + console.log(scroll); + }, [filteredLogs, shouldFollowLogs, scroll]); + + const stopFollowingOnUpScroll = () => { + if (scroll > scrollerRef.current.scrollTop) { + handleFollowChange(false); + } + setScroll(scrollerRef.current.scrollTop); + }; + return (
@@ -54,14 +79,15 @@ export default function LogList({ (scrollerRef.current = ref)} + isScrolling={isScrolling => isScrolling && stopFollowingOnUpScroll()} totalCount={filteredLogs.length} - increaseViewportBy={300} - followOutput={false} + increaseViewportBy={300} // defines loading overlap (in pixels) itemContent={index => { const log = filteredLogs[index]; return ( { +const Toolbar = ({ + wid, + quarantine, + widgetLocalStorage, + setSearchFilter, + shouldFollowLogs, + handleFollowChange +}) => { const theme = useTheme(); const handleClearLogs = () => @@ -33,9 +40,13 @@ const Toolbar = ({ wid, quarantine, widgetLocalStorage, setSearchFilter }) => { - { }; const [searchFilter, setSearchFilter] = useState(''); + const [shouldFollowLogs, setFollow] = useState(true); const logs = widgetData.content?.logs; const template = widgetData.content?.variableFields; @@ -33,6 +34,8 @@ const LogViewerWidget = ({ id }) => { quarantine={quarantine} widgetLocalStorage={widgetLocalStorage} setSearchFilter={setSearchFilter} + shouldFollowLogs={shouldFollowLogs} + handleFollowChange={setFollow} /> {logs && ( { logs={logs} template={template} search={searchFilter} + shouldFollowLogs={shouldFollowLogs} + handleFollowChange={setFollow} /> )} From 5c52267215c18f70986cbd50d240d4912a9b4ef1 Mon Sep 17 00:00:00 2001 From: "szymon.owczarzak" Date: Thu, 9 Dec 2021 09:41:55 +0100 Subject: [PATCH 103/226] Initial config updated with LogViewer widget --- .../src/main/resources/initData/config.json | 33 +++++++++++++++++++ .../main/resources/initData/credentials.json | 9 +++++ .../main/resources/initData/endpoints.json | 7 ++++ 3 files changed, 49 insertions(+) diff --git a/cogboard-app/src/main/resources/initData/config.json b/cogboard-app/src/main/resources/initData/config.json index f89cd6272..8c65cc9b3 100644 --- a/cogboard-app/src/main/resources/initData/config.json +++ b/cogboard-app/src/main/resources/initData/config.json @@ -143,10 +143,23 @@ "switchInterval": 100, "title": "QA/DEV board", "type": "WidgetBoard" + }, + "board-7d6e23ea-78a1-4f89-a5d2-47c499f9657d": { + "autoSwitch": true, + "switchInterval": 60, + "id": "board-7d6e23ea-78a1-4f89-a5d2-47c499f9657d", + "theme": "default", + "widgets": [ + "widget95" + ], + "columns": 4, + "title": "Log Viewer", + "type": "WidgetBoard" } }, "allBoards": [ "board-9ebbc895-41e7-45b3-b6be-2e88b8dd4e5b", + "board-7d6e23ea-78a1-4f89-a5d2-47c499f9657d", "board-acdc6ecc-2bcd-4f8f-80a3-5db8a5db8394", "board-b93ed3ae-698c-43d3-be8c-06331560e65a", "board-7fd129fd-9f2e-42ee-abc4-a1299c3dadc1", @@ -1950,6 +1963,26 @@ "installedThreshold": 2, "excludedBundles": "", "expandContent": false + }, + "widget95": { + "id": "widget95", + "title": "SSH Mock Server Logs", + "config": { + "columns": 4, + "goNewLine": false, + "rows": 3 + }, + "type": "LogViewerWidget", + "disabled": false, + "content": {}, + "isUpdating": false, + "boardId": "board-7d6e23ea-78a1-4f89-a5d2-47c499f9657d", + "endpoint": "endpoint6", + "schedulePeriod": 60, + "path": "/home/mock/example.txt", + "logLinesField": 300, + "logFileSizeField": 50, + "logRecordExpirationField": 5 } } } diff --git a/cogboard-app/src/main/resources/initData/credentials.json b/cogboard-app/src/main/resources/initData/credentials.json index b3e4b8e77..c02debd75 100644 --- a/cogboard-app/src/main/resources/initData/credentials.json +++ b/cogboard-app/src/main/resources/initData/credentials.json @@ -7,6 +7,15 @@ "user": "admin", "label": "Zabbix", "id": "credential1" + }, + { + "sshKeyPassphrase": "", + "sshKey": "", + "token": "", + "password": "TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E", + "user": "mock", + "label": "SSH Cred", + "id": "credential4" } ] } diff --git a/cogboard-app/src/main/resources/initData/endpoints.json b/cogboard-app/src/main/resources/initData/endpoints.json index 5f13287cb..6fe8fc0d5 100644 --- a/cogboard-app/src/main/resources/initData/endpoints.json +++ b/cogboard-app/src/main/resources/initData/endpoints.json @@ -13,6 +13,13 @@ "publicUrl": "http://api-mocks:8080/zabbix/api_jsonrpc.php", "credentials": "credential1", "id": "endpoint3" + }, + { + "label": "SSH Logs Server", + "url": "ssh://ssh-server:2222", + "publicUrl": "", + "credentials": "credential4", + "id": "endpoint6" } ] } \ No newline at end of file From 0ca29f950768542f103077cf2bec10196a6d97f5 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 11 Dec 2021 16:33:39 +0100 Subject: [PATCH 104/226] Code cleanup #436 --- .../widgets/types/LogViewerWidget/LogList/index.js | 9 ++++----- .../widgets/types/LogViewerWidget/LogList/styled.js | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 0719595e2..e165a7c09 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -42,7 +42,6 @@ export default function LogList({ .filter(log => filterByLevel(log, level)) .reverse(); // maybe logs can be sent in correct order - const VariableLogListHeader = () => ( {template.map((name, index) => ( @@ -53,15 +52,12 @@ export default function LogList({ useEffect(() => { if (shouldFollowLogs) { - console.log(scrollerRef.current.scrollHeight); - console.log(scrollerRef.current.scrollTop); scrollerRef.current.scrollTo({ top: scrollerRef.current.scrollHeight, behavior: 'smooth' }); } setScroll(scrollerRef.current.scrollTop); - console.log(scroll); }, [filteredLogs, shouldFollowLogs, scroll]); const stopFollowingOnUpScroll = () => { @@ -71,6 +67,9 @@ export default function LogList({ setScroll(scrollerRef.current.scrollTop); }; + const handleScrollChange = isScrolling => + isScrolling && stopFollowingOnUpScroll(); + return (
@@ -84,7 +83,7 @@ export default function LogList({ (scrollerRef.current = ref)} - isScrolling={isScrolling => isScrolling && stopFollowingOnUpScroll()} + isScrolling={handleScrollChange} totalCount={filteredLogs.length} increaseViewportBy={300} // defines loading overlap (in pixels) itemContent={index => { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 72a7f09c9..e92742e43 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -79,8 +79,8 @@ export const CustomAccordion = styled(Accordion)` background-color: ${COLORS.LIGHT_SHADE}; overflow: hidden; } - &.Mui-expanded { - margin: 0.5em 0 !important; + &&.Mui-expanded { + margin: 0.5em 0; } .MuiAccordionSummary-root { From 9ad7ed0c6fbc4a4192456c119ee824d39171fbf1 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 11 Dec 2021 16:44:19 +0100 Subject: [PATCH 105/226] Move item into function #436 --- .../types/LogViewerWidget/LogList/index.js | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index e165a7c09..dbda96ba2 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -70,6 +70,22 @@ export default function LogList({ const handleScrollChange = isScrolling => isScrolling && stopFollowingOnUpScroll(); + const getLogByIndex = index => { + const log = filteredLogs[index]; + return ( + + ); + }; + return (
@@ -86,21 +102,7 @@ export default function LogList({ isScrolling={handleScrollChange} totalCount={filteredLogs.length} increaseViewportBy={300} // defines loading overlap (in pixels) - itemContent={index => { - const log = filteredLogs[index]; - return ( - - ); - }} + itemContent={getLogByIndex} /> From e2775936dc04aeb24b1420f1f075f1bad31a763e Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 11 Dec 2021 17:36:38 +0100 Subject: [PATCH 106/226] Fix jest App.js test --- cogboard-webapp/package.json | 5 +++++ cogboard-webapp/src/App.test.js | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cogboard-webapp/package.json b/cogboard-webapp/package.json index 8e5007ffd..8319219c6 100644 --- a/cogboard-webapp/package.json +++ b/cogboard-webapp/package.json @@ -72,5 +72,10 @@ "pretty-quick --staged", "git add" ] + }, + "jest": { + "transformIgnorePatterns": [ + "node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)" + ] } } diff --git a/cogboard-webapp/src/App.test.js b/cogboard-webapp/src/App.test.js index a754b201b..a19217900 100644 --- a/cogboard-webapp/src/App.test.js +++ b/cogboard-webapp/src/App.test.js @@ -1,9 +1,18 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import configureStore from './configureStore'; import App from './App'; +const store = configureStore(); + it('renders without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(, div); + ReactDOM.render( + + + , + div + ); ReactDOM.unmountComponentAtNode(div); }); From baefa3301e3554c13a6af696219418d5bc895803 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 11 Dec 2021 21:50:41 +0100 Subject: [PATCH 107/226] Add filterByRexExp unit tests #440 --- .../LogList/filterByRegExp.test.js | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js new file mode 100644 index 000000000..301f87e21 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js @@ -0,0 +1,77 @@ +import { filterByRegExp } from './helpers'; + +const logHeader = { + _id: '61b4c753bc103d391657c49c', + seq: 228, + insertedOn: 1639237459, + date: '2021-12-11T15:44:00', + type: 'DEBUG', + variableData: null +}; + +it('filter by regexp', () => { + const filters = [ + { + id: 'filter-3ea57f8b-6c1c-4e2f-bbc5-760aa0526e8e', + checked: true, + regExp: '^a', + label: 'starts with a' + }, + { + id: 'filter-2afee54b-8ab6-4a0c-81db-9c92e7bf5203', + checked: true, + label: 'velit', + regExp: 'velit' + }, + { + id: 'filter-b9b72673-e6ca-481d-996a-293527032764', + checked: true, + label: 'starts with F', + regExp: '^F' + }, + { + id: 'filter-c9dac795-43c6-4a57-9329-5dd1573c9bf4', + checked: true, + label: 'ends with suscipit', + regExp: 'suscipit$' + } + ]; + + const log = { + ...logHeader, + variableData: [ + { + header: 'FelixStartLevel', + description: 'No description' + }, + { + header: 'id velit. vel pretium. ipsum suscipit', + description: 'No message description' + } + ] + }; + const emptyLog = { + ...logHeader, + variableData: [ + { + header: '', + description: '' + } + ] + }; + + expect(filterByRegExp(log, [])).toEqual(true); + + // starts with a; velit + expect(filterByRegExp(log, [filters[0], filters[1]])).toEqual(false); + // starts with a(OFF); velit + expect( + filterByRegExp(log, [{ ...filters[0], checked: false }, filters[1]]) + ).toEqual(true); + // velit; starts with F; ends with suscipit + expect(filterByRegExp(log, [filters[1], filters[2], filters[3]])).toEqual( + true + ); + + expect(filterByRegExp(emptyLog, filters)).toEqual(false); +}); From a229e0f4463bc8d5162ba48efcfe9280ae8d9f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 12 Dec 2021 17:33:12 +0100 Subject: [PATCH 108/226] Add quarantine info message, change quarantine matching system --- .../com/cognifide/cogboard/logStorage/LogStorage.kt | 6 +++--- .../widget/type/logviewer/LogViewerWidget.kt | 2 +- cogboard-local-compose.yml | 2 +- .../Toolbar/QuarantineModal/QuarantineForm.js | 12 ++++++++++-- .../Toolbar/QuarantineModal/styled.js | 11 +++++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index b3923616b..0ac338e06 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -11,7 +11,7 @@ import com.mongodb.client.model.Filters.eq import com.mongodb.client.model.Filters.lt import com.mongodb.client.model.Filters.`in` import com.mongodb.client.model.Filters.regex -import com.mongodb.client.model.Filters.nor +import com.mongodb.client.model.Filters.or import com.mongodb.client.model.Indexes import com.mongodb.client.model.Sorts.descending import com.mongodb.client.model.ReplaceOptions @@ -135,7 +135,7 @@ class LogStorage( private fun filter(logs: MutableList) { val regexes = enabledRegexes if (regexes.isEmpty()) { return } - logs.retainAll { log -> + logs.removeAll { log -> log.variableData.any { variable -> regexes.any { it.containsMatchIn(variable.header) } } @@ -151,7 +151,7 @@ class LogStorage( if (regexes.isEmpty()) { return } - collection.deleteMany(nor(regexes)) + collection.deleteMany(or(regexes)) } /** Downloads new logs and inserts the to the database. Returns the number of inserted logs. */ diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index e01cdbe1c..ce15c5b90 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -117,7 +117,7 @@ class LogViewerWidget( companion object { private const val DEFAULT_ID = "0" - private const val DEFAULT_LOG_LINES = 100L + private const val DEFAULT_LOG_LINES = 1000L private const val DEFAULT_LOG_FILE_SIZE = 50L private const val DEFAULT_LOG_EXPIRATION_DAYS = 5L diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 08727bc5f..329c2d671 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -38,7 +38,7 @@ services: # - "18092:18092" mongo: - image: mongo + image: mongo:4 restart: always environment: MONGO_INITDB_ROOT_USERNAME: "root" diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js index 0d5b585cd..f8f7cbd1e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js @@ -3,7 +3,8 @@ import { createValidationSchema } from '../../../../../validation'; import { useFormData } from '../../../../../../hooks'; import DynamicForm from '../../../../../DynamicForm'; import { Button } from '@material-ui/core'; -import CancelButton from '../../../../../CancelButton'; +import InfoIcon from '@material-ui/icons/Info'; +import { StyledHorizontalContainer, StyledCancelButton } from './styled'; const QuarantineForm = ({ filters, @@ -32,6 +33,13 @@ const QuarantineForm = ({ return (
+ + +

+ Logs, any fields of which will be matched by the regular expression, + will not be stored in the database or displayed. +

+
Save - diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js index 27ffc04e3..9b9612431 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js @@ -1,7 +1,18 @@ import styled from '@emotion/styled/macro'; import { Button } from '@material-ui/core'; +import CancelButton from '../../../../../CancelButton'; export const StyledExitButton = styled(Button)` margin-top: 12px; `; + +export const StyledCancelButton = styled(CancelButton)` + margin-left: 20px; +`; + +export const StyledHorizontalContainer = styled.div` + display: flex; + align-items: center; + gap: 12px; +`; From e5a4cc528b7edad436592233280de2c5a70ccce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 12 Dec 2021 20:02:21 +0100 Subject: [PATCH 109/226] Clear logs based on the date of the last one --- .../widgets/types/LogViewerWidget/Toolbar/index.js | 12 +++++++++--- .../widgets/types/LogViewerWidget/index.js | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 68b039218..4000f6f2d 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -18,12 +18,18 @@ const Toolbar = ({ widgetLocalStorage, setSearchFilter, shouldFollowLogs, - handleFollowChange + handleFollowChange, + firstLog }) => { const theme = useTheme(); - const handleClearLogs = () => - saveDateSpan(widgetLocalStorage, { begin: moment(), end: null }); + const handleClearLogs = () => { + let date = firstLog?.date; + if (date) { + let beginDate = moment(date).add(1, 'seconds'); + saveDateSpan(widgetLocalStorage, { begin: beginDate, end: null }); + } + }; return ( diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 649c2b240..f5ad47f7b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -36,6 +36,7 @@ const LogViewerWidget = ({ id }) => { setSearchFilter={setSearchFilter} shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} + firstLog={logs[0]} /> {logs && ( Date: Wed, 15 Dec 2021 13:06:48 +0100 Subject: [PATCH 110/226] Setup LogsViewer technical documentation --- .../types/LogViewerWidget/LogsViewer.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md new file mode 100644 index 000000000..a94472fc8 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -0,0 +1,40 @@ +# Logs Viewer + +- `widgetLocalStorage` uses browser local storage with widget id as a key +- `searchFilter` state of searchbox highlight (not to be confused with state of searchbox input) +- `template` log template (array of column names, does not include `type` and `date`) + +## Toolbar + +### SearchInput + +Has its own state. If input contains at least `minLetters` characters, value will be sent to higlight mechanism. + +### FilterPicker + +Use browser local storage. + +- `filters` ui disabled when no filter is defined. +- `logLevel` +- `advanced` filters management + +### DateRangePicker + +Uses browser local storage and `momentjs`. Ignores seconds. + +### Follow logs + +_TODO_ + +### Clear logs + +Hides all logs that have been delivered before click. +Sets begin date of `DateRangePicker` to arrival date of last log. + +### Quarantine + +_TODO_ + +## LogList + +_TODO_ From ff3254923d6d9e0d6d9f7db2f66123db0787e84a Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 15 Dec 2021 15:55:16 +0100 Subject: [PATCH 111/226] Add log list to docs --- .../types/LogViewerWidget/LogsViewer.md | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index a94472fc8..3475b09b6 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -1,8 +1,8 @@ # Logs Viewer -- `widgetLocalStorage` uses browser local storage with widget id as a key -- `searchFilter` state of searchbox highlight (not to be confused with state of searchbox input) -- `template` log template (array of column names, does not include `type` and `date`) +- `widgetLocalStorage` \* uses browser local storage with widget id as a key +- `searchFilter` - state of searchbox highlight (not to be confused with state of searchbox input) +- `template` - log template (array of column names, does not include `type` and `date`) ## Toolbar @@ -14,9 +14,9 @@ Has its own state. If input contains at least `minLetters` characters, value wil Use browser local storage. -- `filters` ui disabled when no filter is defined. +- `filters` - ui disabled when no filter is defined. - `logLevel` -- `advanced` filters management +- `advanced` - filters management ### DateRangePicker @@ -28,7 +28,7 @@ _TODO_ ### Clear logs -Hides all logs that have been delivered before click. +Hides all logs that have been delivered before usage. Sets begin date of `DateRangePicker` to arrival date of last log. ### Quarantine @@ -37,4 +37,27 @@ _TODO_ ## LogList +Displays logs and column names. +Will not render if logs are not provided (template required). + +### VariableGridSchema + +Component which provides equal columns widths for `Header` and `LogEntry`. + +- `getGridTemplate` - by default column gets `1fr` of space, but `message` column will receive `3fr`. Returns `grid-template-columns`. + +### Logs + +Virtuoso is used to virtualize list. + +### Highlighting + +There is double check for highlight - one for marker in the left-upper corner (`isLogHighlighted`), second used in `LogEntry` for text highlighting (`highlightText`). + +### Following logs + _TODO_ + +### LogEntry + +Every log column consists of `header` (accordion summary) and `description` (accordion details). From 9c5409d6fa12480e544f7cf53ee919967d252d76 Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 17 Dec 2021 17:52:31 +0100 Subject: [PATCH 112/226] fix local storage sentence --- .../components/widgets/types/LogViewerWidget/LogsViewer.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index 3475b09b6..abb14d6d9 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -12,15 +12,16 @@ Has its own state. If input contains at least `minLetters` characters, value wil ### FilterPicker -Use browser local storage. +Uses local storage of the browser. -- `filters` - ui disabled when no filter is defined. +- `filters` + - ui disabled when no filter is defined. - `logLevel` - `advanced` - filters management ### DateRangePicker -Uses browser local storage and `momentjs`. Ignores seconds. +Uses local storage of the browser and `momentjs` library. Ignores seconds. ### Follow logs From 75c2f6bf7ddc155f5d20dd3421917660af25caa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sat, 18 Dec 2021 16:03:03 +0100 Subject: [PATCH 113/226] Moved quarantine button to advanced modal #437 --- .../FilterPicker/AdvancedFiltersMenu/index.js | 4 +++- .../Toolbar/FilterPicker/index.js | 8 ++++++-- .../Toolbar/QuarantineModal/index.js | 16 ++++++++++------ .../Toolbar/QuarantineModal/styled.js | 2 +- .../types/LogViewerWidget/Toolbar/index.js | 14 ++++++++------ 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 5c2128c79..5700d51c4 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -2,6 +2,7 @@ import React from 'react'; import { useToggle } from '../../../../../../../hooks'; import { v4 } from 'uuid'; import { getFilters, saveFilters } from '../helpers'; +import QuarantineModal from '../../QuarantineModal'; import { Button, @@ -19,7 +20,7 @@ import DeleteItem from '../../../../../../DeleteItem'; import FilterForm from './FilterForm'; import { StyledExitButton } from './styled'; -const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { +const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); const filters = getFilters(widgetLocalStorage); @@ -117,6 +118,7 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { + { +const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { const regExpFilters = getFilters(widgetLocalStorage); const [logLevel, setLogLevel] = useState('info'); @@ -95,7 +95,11 @@ const FilterPicker = ({ widgetLocalStorage }) => { ))} - + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index 63c8e729b..7469a9150 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -2,14 +2,13 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { v4 } from 'uuid'; import { - Button, List, ListItem, ListItemText, ListItemSecondaryAction, Switch } from '@material-ui/core'; -import { StyledExitButton } from './styled'; +import { StyledButton } from './styled'; import { getIsAuthenticated } from '../../../../../../selectors'; import { useToggle } from '../../../../../../hooks'; import { postWidgetContentUpdate } from '../../../../../../utils/fetch'; @@ -97,9 +96,14 @@ const QuarantineModal = ({ wid, quarantine }) => { return ( <> - + { - { fullWidth > Exit - + ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js index 27ffc04e3..7487aa3b7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js @@ -2,6 +2,6 @@ import styled from '@emotion/styled/macro'; import { Button } from '@material-ui/core'; -export const StyledExitButton = styled(Button)` +export const StyledButton = styled(Button)` margin-top: 12px; `; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 31eed102a..b7293ab6b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -7,8 +7,6 @@ import DateRangePicker from './DateRangePicker'; import GetAppIcon from '@material-ui/icons/GetApp'; import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; -import QuarantineModal from './QuarantineModal'; - const Toolbar = ({ wid, quarantine, widgetLocalStorage }) => { const theme = useTheme(); @@ -18,7 +16,11 @@ const Toolbar = ({ wid, quarantine, widgetLocalStorage }) => { - + @@ -32,9 +34,9 @@ const Toolbar = ({ wid, quarantine, widgetLocalStorage }) => { Clear logs - - - + {/**/} + {/* */} + {/**/} ); }; From 49e0d5969010ba7ff1fc1eb523dd0f68c2daabf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sat, 18 Dec 2021 20:32:34 +0100 Subject: [PATCH 114/226] Added parser field to LogsViewer configuration modal #438 --- .../widgets/dialogFields/ParserTypeInput.js | 21 +++++++++++++++++++ .../components/widgets/dialogFields/index.js | 7 +++++++ .../src/components/widgets/index.js | 3 ++- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js diff --git a/cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js b/cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js new file mode 100644 index 000000000..bf98baff5 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js @@ -0,0 +1,21 @@ +import React from 'react'; +import DropdownField from '../../DropdownField'; +import { MenuItem } from '@material-ui/core'; + +const ParserTypeInput = props => { + const parsers = [{ id: 'mock', label: 'Mock Parser' }]; + + return ( + + {parsers => + parsers.map(({ id, label }) => ( + + {label} + + )) + } + + ); +}; + +export default ParserTypeInput; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index 4dffc4342..e1bc4fa84 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -38,6 +38,7 @@ import LinkListInput from './LinkListInput'; import ToDoListInput from './ToDoListinput'; import WidgetTypeField from './WidgetTypeField'; import FileTextInput from './FileTextInput'; +import ParserTypeInput from './ParserTypeInput'; const dialogFields = { LabelField: { @@ -643,6 +644,12 @@ const dialogFields = { name: 'reasonField', label: 'Reason', validator: () => string() + }, + LogParserField: { + component: ParserTypeInput, + name: 'logParserField', + label: 'Log parser type', + validator: () => string() } }; diff --git a/cogboard-webapp/src/components/widgets/index.js b/cogboard-webapp/src/components/widgets/index.js index 0230f2df8..1fb3cf7eb 100644 --- a/cogboard-webapp/src/components/widgets/index.js +++ b/cogboard-webapp/src/components/widgets/index.js @@ -210,7 +210,8 @@ const widgetTypes = { 'Path', 'LogLinesField', 'LogFileSizeField', - 'LogRecordExpirationField' + 'LogRecordExpirationField', + 'LogParserField' ], validationConstraints: { SchedulePeriod: { min: 3 }, From 074a915fab411988b1a224bff09a400b5b598d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sat, 18 Dec 2021 21:14:57 +0100 Subject: [PATCH 115/226] Adjusted backend to new config field #438 --- .../com/cognifide/cogboard/CogboardConstants.kt | 1 + .../com/cognifide/cogboard/ssh/auth/SSHAuthData.kt | 2 +- .../cogboard/widget/type/logviewer/LogViewerWidget.kt | 2 +- .../logviewer/logparser/LogParserStrategyFactory.kt | 11 +++++++---- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt index 310558c76..07f3482cc 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt @@ -49,6 +49,7 @@ class CogboardConstants { const val LOG_LINES = "logLinesField" const val LOG_FILE_SIZE = "logFileSizeField" const val LOG_EXPIRATION_DAYS = "logRecordExpirationField" + const val LOG_PARSER = "logParserField" const val REQUEST_ID = "requestId" const val PUBLIC_URL = "publicUrl" const val USER = "user" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index d765acfbd..9550c5bfd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -24,7 +24,7 @@ class SSHAuthData(private val config: JsonObject) { val uri = URI.create(uriString) host = uri.host port = uri.port - if(authenticationType == SSH_KEY) { + if (authenticationType == SSH_KEY) { prepareForSSHKeyUsage() } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index c43f23215..dc9cfc287 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -77,7 +77,7 @@ class LogViewerWidget( private fun determineLogParsingStrategy() = LogParserStrategyFactory() - .build(LogParserStrategyFactory.Type.MOCK) + .build(config.getString(Props.LOG_PARSER)) companion object { private const val DEFAULT_ID = "0" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt index e71a74c39..ceb4c498b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt @@ -5,10 +5,13 @@ class LogParserStrategyFactory { MOCK } - fun build(type: Type): LogParserStrategy { - return when (type) { - Type.MOCK -> MockLogParserStrategy() - else -> throw UnknownParserTypeException("Unknown log parsing type") + fun build(typeStr: String): LogParserStrategy { + return try { + when (Type.valueOf(typeStr.toUpperCase())) { + Type.MOCK -> MockLogParserStrategy() + } + } catch (e : IllegalArgumentException) { + throw UnknownParserTypeException("Unknown log parsing type") } } } From d75495462b7783d3f91e4f7c3ba8d7037e7369d0 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 19 Dec 2021 01:53:56 +0100 Subject: [PATCH 116/226] Add create widget test #440 --- functional/cypress-tests/cypress/fixtures/Widgets.js | 6 ++++++ .../cypress-tests/cypress/support/widgetDynamicTab.js | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/functional/cypress-tests/cypress/fixtures/Widgets.js b/functional/cypress-tests/cypress/fixtures/Widgets.js index 40e8ea226..d3b390f4d 100644 --- a/functional/cypress-tests/cypress/fixtures/Widgets.js +++ b/functional/cypress-tests/cypress/fixtures/Widgets.js @@ -138,6 +138,12 @@ module.exports = { } ] }, + logsViewer: { + name: 'Log Viewer', + endpoint: 'endpoint6', + schedulePeriod: '60', + path: '/home/mock/example.txt', + }, serviceCheck: { name: 'Service Check', schedulePeriod: '3', diff --git a/functional/cypress-tests/cypress/support/widgetDynamicTab.js b/functional/cypress-tests/cypress/support/widgetDynamicTab.js index 51bca573e..4e792b5c7 100644 --- a/functional/cypress-tests/cypress/support/widgetDynamicTab.js +++ b/functional/cypress-tests/cypress/support/widgetDynamicTab.js @@ -86,6 +86,13 @@ export function fillLinksList() { } } +export function fillLogsViewer() { + cy.get('[data-cy="widget-form-endpoint-input"]').click(); + cy.get(`[data-value="${Widgets.logsViewer.endpoint}"]`).click(); + cy.fillSchedulePeriod(Widgets.logsViewer.schedulePeriod); + cy.get('[data-cy="widget-form-path-input"]').type(Widgets.logsViewer.path); +} + export function fillServiceCheck() { cy.fillSchedulePeriod(Widgets.serviceCheck.schedulePeriod); cy.get('[data-cy="widget-form-request-method-input"]').click(); @@ -218,6 +225,9 @@ export function fillDynamicTab(widget) { case 'Link List': selectTabAndFillData(fillLinksList); break; + case 'Log Viewer': + selectTabAndFillData(fillLogsViewer); + break; case 'Service Check': selectTabAndFillData(fillServiceCheck); break; From 34e70394f18fc59bfab2bb5bcd9c44edf4ef47f9 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 19 Dec 2021 16:57:19 +0100 Subject: [PATCH 117/226] Add create widget validation #440 --- .../cypress-tests/cypress/support/widgetAssertions.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/functional/cypress-tests/cypress/support/widgetAssertions.js b/functional/cypress-tests/cypress/support/widgetAssertions.js index 2e826b6b6..b3f6eb3ab 100644 --- a/functional/cypress-tests/cypress/support/widgetAssertions.js +++ b/functional/cypress-tests/cypress/support/widgetAssertions.js @@ -82,6 +82,12 @@ export function validateLinkList(widget) { } } +export function validateLogsViewer(widget) { + widget.assertTitle(widget.title) + widget.assertText('p', 'Filters') + widget.assertText('p', 'Level') +} + export function validateServiceCheck(widget) { widget .assertBackground('rgb(1, 148, 48)') @@ -182,6 +188,8 @@ export function validateWidgetConfig(widget) { case 'Link List': validateLinkList(widget); break; + case 'Log Viewer': + validateLogsViewer(widget); case 'Service Check': validateServiceCheck(widget); break; From 24cf7e211a37a3f55924e090c20ab532f175c4bb Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 19 Dec 2021 18:02:30 +0100 Subject: [PATCH 118/226] Add add filter test #440 --- .../types/LogViewerWidget/LogList/LogEntry.js | 3 +- .../AdvancedFiltersMenu/FilterForm.js | 4 +- .../FilterPicker/AdvancedFiltersMenu/index.js | 7 +- .../cypress/integration/logsViewer.js | 68 +++++++++++++++++++ .../cypress-tests/cypress/support/widget.js | 11 ++- .../cypress/support/widgetAssertions.js | 6 +- 6 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 functional/cypress-tests/cypress/integration/logsViewer.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index b3ec2a4e1..e9bfb0fee 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -28,6 +28,7 @@ const LogEntry = ({ {variableData.map((entry, index) => { const entryText = description ? entry.description : entry.header; @@ -42,7 +43,7 @@ const LogEntry = ({ }; return ( - + setExpanded(!expanded)} expandIcon={expanded && } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js index 57fc83767..c47d0f086 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js @@ -44,13 +44,13 @@ const FilterForm = ({ color="secondary" variant="contained" type="submit" - data-cy="endpoint-form-submit-button" + data-cy="filter-form-submit-button" > Save ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 5c2128c79..937938ede 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -96,7 +96,12 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage }) => { return ( <> - { + let widget; + + before(() => { + cy.visit('/'); + cy.login(); + cy.openDrawer(); + cy.chooseDashboard(dashboardName); + cy.clickAddWidgetButton(); + widget = createWidget(logsViewer.name).configure(false, { + cols: 8, + rows: 2 + }); + }); + + describe('Filters', () => { + const label = 'starts with a'; + const regExp = '^a'; + + it('Advanced filters modal shows', () => { + widget.click('[data-cy="advanced-filters-button"]'); + widget.assertText('h2', 'Advanced filters'); + }); + + it('Adding filter works', () => { + widget.click('[data-cy="add-filter-add-button"]'); + + widget.assertText('h2', 'Add new filter'); + cy.get('[data-cy="filter-form-label-input"]') + .clear() + .type(label); + cy.get('[data-cy="filter-form-reg-exp-input"]').type(regExp); + widget.click('[data-cy="filter-form-submit-button"]'); + + widget.assertText('span.MuiListItemText-primary', label); + widget.isChecked( + '.MuiListItemSecondaryAction-root input[type="checkbox"]', + true + ); + + widget.click('[data-cy="advanced-filters-menu-exit-button"]'); + }); + + it('Filter shows in multiselect', () => { + widget.assertText('.MuiChip-label', label); + }); + + it('Filter filters logs', () => { + cy.get('[data-cy="log-entry"] ').each(log => { + cy.wrap(log).contains( + '[data-cy="log-variable-data"] p', + new RegExp(regExp) + ); + }); + }); + }); + + // doesn't work + + // after(() => { + // widget.remove() + // }) +}); diff --git a/functional/cypress-tests/cypress/support/widget.js b/functional/cypress-tests/cypress/support/widget.js index d04b68886..78b06f3e1 100644 --- a/functional/cypress-tests/cypress/support/widget.js +++ b/functional/cypress-tests/cypress/support/widget.js @@ -7,8 +7,15 @@ class Widget { this.title = `Test-${name}`; } - configure(disabled) { - cy.fillNewWidgetGeneral(this.name, this.title, false, disabled, 2, 1); + configure(disabled, size = { cols: 2, rows: 1 }) { + cy.fillNewWidgetGeneral( + this.name, + this.title, + false, + disabled, + size.cols, + size.rows + ); fillDynamicTab(this); cy.confirmAddWidget(); return this; diff --git a/functional/cypress-tests/cypress/support/widgetAssertions.js b/functional/cypress-tests/cypress/support/widgetAssertions.js index b3f6eb3ab..9eec320b9 100644 --- a/functional/cypress-tests/cypress/support/widgetAssertions.js +++ b/functional/cypress-tests/cypress/support/widgetAssertions.js @@ -83,9 +83,8 @@ export function validateLinkList(widget) { } export function validateLogsViewer(widget) { - widget.assertTitle(widget.title) - widget.assertText('p', 'Filters') - widget.assertText('p', 'Level') + widget.assertText('p', 'Filters'); + widget.assertText('p', 'Level'); } export function validateServiceCheck(widget) { @@ -190,6 +189,7 @@ export function validateWidgetConfig(widget) { break; case 'Log Viewer': validateLogsViewer(widget); + break; case 'Service Check': validateServiceCheck(widget); break; From d7558ce0b722aa06e19952fd9008da62d42360eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 19 Dec 2021 20:23:44 +0100 Subject: [PATCH 119/226] Fix the bugs in the code --- .../components/widgets/types/LogViewerWidget/Toolbar/index.js | 4 ++-- .../src/components/widgets/types/LogViewerWidget/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 4000f6f2d..a0d856832 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -24,9 +24,9 @@ const Toolbar = ({ const theme = useTheme(); const handleClearLogs = () => { - let date = firstLog?.date; + const date = firstLog?.date; if (date) { - let beginDate = moment(date).add(1, 'seconds'); + const beginDate = moment(date).add(1, 'seconds'); saveDateSpan(widgetLocalStorage, { begin: beginDate, end: null }); } }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index f5ad47f7b..9f219365e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -36,7 +36,7 @@ const LogViewerWidget = ({ id }) => { setSearchFilter={setSearchFilter} shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} - firstLog={logs[0]} + firstLog={logs && logs[0]} /> {logs && ( Date: Sun, 19 Dec 2021 22:58:17 +0100 Subject: [PATCH 120/226] Add filters multiselect test #440 --- .../Toolbar/FilterPicker/index.js | 40 ++++++++--------- .../Toolbar/FilterPicker/styled.js | 23 +++++++++- .../cypress/integration/logsViewer.js | 44 +++++++++++++------ 3 files changed, 70 insertions(+), 37 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index f0a50559d..50a7ee80c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -1,17 +1,14 @@ import React from 'react'; import { getFilters, getLevel, saveFilters, saveLevel } from './helpers'; - import logLevels from '../../logLevels'; +import { MenuItem, FormControl, InputLabel, Tooltip } from '@material-ui/core'; import { - Select, - Chip, - MenuItem, - FormControl, - InputLabel, - Tooltip -} from '@material-ui/core'; -import { ScrollableBox } from './styled'; + ScrollableBox, + FiltersSelect, + LogLevelSelect, + StyledChip +} from './styled'; import ToolbarGroup from '../ToolbarGroup'; import AdvancedFiltersMenu from './AdvancedFiltersMenu'; @@ -44,14 +41,12 @@ const FilterPicker = ({ widgetLocalStorage }) => { {regExpFilters.length > 0 ? `Filters` : `No filters defined`} - + Log level - + diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js index 312484d59..c1cea6e28 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js @@ -1,5 +1,5 @@ import styled from '@emotion/styled/macro'; -import { Box } from '@material-ui/core'; +import { Box, Select, Chip } from '@material-ui/core'; export const ScrollableBox = styled(Box)` overflow-x: scroll; @@ -9,3 +9,24 @@ export const ScrollableBox = styled(Box)` display: none; } `; + +export const LogLevelSelect = styled(Select)` + width: 6rem; +`; +LogLevelSelect.defaultProps = { + size: 'small' +}; + +export const FiltersSelect = styled(Select)` + width: 12rem; +`; +FiltersSelect.defaultProps = { + size: 'small' +}; + +export const StyledChip = styled(Chip)` + margin-right: 0.25rem; +`; +StyledChip.defaultProps = { + size: 'small' +}; diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 31efee377..d8bee5751 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -19,8 +19,17 @@ describe('Logs Viewer', () => { }); describe('Filters', () => { - const label = 'starts with a'; - const regExp = '^a'; + const logsMatchFilter = regExp => { + cy.get('[data-cy="log-entry"] ').each(log => { + cy.wrap(log).contains( + '[data-cy="log-variable-data"] p', + new RegExp(regExp) + ); + }); + }; + + const first = { label: 'starts with a', regExp: '^a' }; + const second = { label: 'ends with a', regExp: 'a$' }; it('Advanced filters modal shows', () => { widget.click('[data-cy="advanced-filters-button"]'); @@ -33,11 +42,11 @@ describe('Logs Viewer', () => { widget.assertText('h2', 'Add new filter'); cy.get('[data-cy="filter-form-label-input"]') .clear() - .type(label); - cy.get('[data-cy="filter-form-reg-exp-input"]').type(regExp); + .type(first.label); + cy.get('[data-cy="filter-form-reg-exp-input"]').type(first.regExp); widget.click('[data-cy="filter-form-submit-button"]'); - widget.assertText('span.MuiListItemText-primary', label); + widget.assertText('span.MuiListItemText-primary', first.label); widget.isChecked( '.MuiListItemSecondaryAction-root input[type="checkbox"]', true @@ -46,20 +55,27 @@ describe('Logs Viewer', () => { widget.click('[data-cy="advanced-filters-menu-exit-button"]'); }); - it('Filter shows in multiselect', () => { - widget.assertText('.MuiChip-label', label); + it('Filter removes logs correctly', () => { + logsMatchFilter(first.regExp); }); - it('Filter filters logs', () => { - cy.get('[data-cy="log-entry"] ').each(log => { - cy.wrap(log).contains( - '[data-cy="log-variable-data"] p', - new RegExp(regExp) - ); - }); + it('Filter multiselect dialog works', () => { + widget.assertText('[data-cy="filters-chip"] .MuiChip-label', first.label); + widget.click('[data-cy="filters-chip"] .MuiChip-deleteIcon'); + cy.contains( + '[data-cy="filters-chip"] .MuiChip-label', + first.label + ).should('not.exist'); + + widget.click('[data-cy="filters-menu"]'); + widget.click('[data-cy="filters-menu-option"]'); + cy.get('[data-cy="filters-menu-option"]').type('{esc}'); + widget.assertText('[data-cy="filters-chip"] .MuiChip-label', first.label); }); }); + // data-cy="filters-chip" + // doesn't work // after(() => { From 411578f87b8093c2eb28153cb44f97bff494bb8f Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 20 Dec 2021 00:39:29 +0100 Subject: [PATCH 121/226] Add editing filter test #440 --- .../AdvancedFiltersMenu/EditFilter.js | 2 +- .../cypress/integration/logsViewer.js | 97 ++++++++++++++----- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js index c67c22fd6..9565fcf63 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/EditFilter.js @@ -19,7 +19,7 @@ const EditFilter = ({ id, filters, editAction }) => { return ( <> - + diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index d8bee5751..84c778112 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -28,11 +28,38 @@ describe('Logs Viewer', () => { }); }; - const first = { label: 'starts with a', regExp: '^a' }; - const second = { label: 'ends with a', regExp: 'a$' }; + const openAdvancedMenu = () => + widget.click('[data-cy="advanced-filters-button"]'); + const closeAdvancedMenu = () => + widget.click('[data-cy="advanced-filters-menu-exit-button"]'); + const isFilterVisibleInAdvancedMenu = label => { + widget.assertText('span.MuiListItemText-primary', label); + widget.isChecked( + '.MuiListItemSecondaryAction-root input[type="checkbox"]', + true + ); + }; + const fillFormField = (field, value) => { + cy.get(`[data-cy="filter-form-${field}-input"]`) + .clear() + .type(value); + }; + const submitForm = () => + widget.click('[data-cy="filter-form-submit-button"]'); + + const filters = [ + { + label: 'starts with a', + regExp: '^a' + }, + { + label: 'ends with a', + regExp: 'a$' + } + ]; it('Advanced filters modal shows', () => { - widget.click('[data-cy="advanced-filters-button"]'); + openAdvancedMenu(); widget.assertText('h2', 'Advanced filters'); }); @@ -40,41 +67,65 @@ describe('Logs Viewer', () => { widget.click('[data-cy="add-filter-add-button"]'); widget.assertText('h2', 'Add new filter'); - cy.get('[data-cy="filter-form-label-input"]') - .clear() - .type(first.label); - cy.get('[data-cy="filter-form-reg-exp-input"]').type(first.regExp); - widget.click('[data-cy="filter-form-submit-button"]'); + fillFormField('label', filters[0].label); + fillFormField('reg-exp', filters[0].regExp); + submitForm(); - widget.assertText('span.MuiListItemText-primary', first.label); - widget.isChecked( - '.MuiListItemSecondaryAction-root input[type="checkbox"]', - true - ); - - widget.click('[data-cy="advanced-filters-menu-exit-button"]'); + isFilterVisibleInAdvancedMenu(filters[0].label); + closeAdvancedMenu(); }); - it('Filter removes logs correctly', () => { - logsMatchFilter(first.regExp); + it('Filter filters logs correctly', () => { + logsMatchFilter(filters[0].regExp); }); - it('Filter multiselect dialog works', () => { - widget.assertText('[data-cy="filters-chip"] .MuiChip-label', first.label); + it('Multiselect dialog works', () => { + widget.assertText( + '[data-cy="filters-chip"] .MuiChip-label', + filters[0].label + ); widget.click('[data-cy="filters-chip"] .MuiChip-deleteIcon'); cy.contains( '[data-cy="filters-chip"] .MuiChip-label', - first.label + filters[0].label ).should('not.exist'); widget.click('[data-cy="filters-menu"]'); widget.click('[data-cy="filters-menu-option"]'); cy.get('[data-cy="filters-menu-option"]').type('{esc}'); - widget.assertText('[data-cy="filters-chip"] .MuiChip-label', first.label); + widget.assertText( + '[data-cy="filters-chip"] .MuiChip-label', + filters[0].label + ); }); - }); - // data-cy="filters-chip" + it('Editing filter works', () => { + openAdvancedMenu(); + widget.click('[data-cy="edit-filter-edit-button"]'); + widget.assertText('h2', 'Edit filter'); + cy.get('[data-cy="filter-form-label-input"]').should( + 'have.attr', + 'value', + filters[0].label + ); + widget.assertText( + '[data-cy="filter-form-reg-exp-input"]', + filters[0].regExp + ); + fillFormField('label', filters[1].label); + fillFormField('reg-exp', filters[1].regExp); + submitForm(); + + isFilterVisibleInAdvancedMenu(filters[1].label); + closeAdvancedMenu(); + + logsMatchFilter(filters[1].regExp); + widget.assertText( + '[data-cy="filters-chip"] .MuiChip-label', + filters[1].label + ); + }); + }); // doesn't work From f9916534762211d3ccdafd4bd8dd95def9b794f9 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 20 Dec 2021 01:34:26 +0100 Subject: [PATCH 122/226] Add removal test, complex filtering test #440 --- .../cypress/fixtures/logsViewer.js | 10 ++ .../cypress/integration/logsViewer.js | 104 +++++++----------- .../cypress/support/logsViewer/filters.js | 44 ++++++++ 3 files changed, 91 insertions(+), 67 deletions(-) create mode 100644 functional/cypress-tests/cypress/fixtures/logsViewer.js create mode 100644 functional/cypress-tests/cypress/support/logsViewer/filters.js diff --git a/functional/cypress-tests/cypress/fixtures/logsViewer.js b/functional/cypress-tests/cypress/fixtures/logsViewer.js new file mode 100644 index 000000000..d4459fed1 --- /dev/null +++ b/functional/cypress-tests/cypress/fixtures/logsViewer.js @@ -0,0 +1,10 @@ +export const filters = [ + { + label: 'starts with a', + regExp: '^a' + }, + { + label: 'ends with a', + regExp: 'a$' + } +]; \ No newline at end of file diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 84c778112..a38d504a5 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -1,5 +1,7 @@ import { logsViewer } from '../fixtures/Widgets'; import { createWidget } from '../support/widget'; +import { openAdvancedMenu, closeAdvancedMenu, addFilter, isFilterVisibleInAdvancedMenu, fillFormField, logsMatchFilter, submitForm, assertChip} from '../support/logsViewer/filters'; +import { filters } from '../fixtures/logsViewer'; const dashboardName = 'Welcome to Cogboard'; @@ -19,87 +21,37 @@ describe('Logs Viewer', () => { }); describe('Filters', () => { - const logsMatchFilter = regExp => { - cy.get('[data-cy="log-entry"] ').each(log => { - cy.wrap(log).contains( - '[data-cy="log-variable-data"] p', - new RegExp(regExp) - ); - }); - }; - - const openAdvancedMenu = () => - widget.click('[data-cy="advanced-filters-button"]'); - const closeAdvancedMenu = () => - widget.click('[data-cy="advanced-filters-menu-exit-button"]'); - const isFilterVisibleInAdvancedMenu = label => { - widget.assertText('span.MuiListItemText-primary', label); - widget.isChecked( - '.MuiListItemSecondaryAction-root input[type="checkbox"]', - true - ); - }; - const fillFormField = (field, value) => { - cy.get(`[data-cy="filter-form-${field}-input"]`) - .clear() - .type(value); - }; - const submitForm = () => - widget.click('[data-cy="filter-form-submit-button"]'); - - const filters = [ - { - label: 'starts with a', - regExp: '^a' - }, - { - label: 'ends with a', - regExp: 'a$' - } - ]; - - it('Advanced filters modal shows', () => { + + it('Advanced filters modal', () => { openAdvancedMenu(); widget.assertText('h2', 'Advanced filters'); + closeAdvancedMenu(); }); - it('Adding filter works', () => { - widget.click('[data-cy="add-filter-add-button"]'); - - widget.assertText('h2', 'Add new filter'); - fillFormField('label', filters[0].label); - fillFormField('reg-exp', filters[0].regExp); - submitForm(); + it('Adding filter', () => { + openAdvancedMenu(); + addFilter(filters[0]) - isFilterVisibleInAdvancedMenu(filters[0].label); + isFilterVisibleInAdvancedMenu(widget, filters[0].label); closeAdvancedMenu(); }); - it('Filter filters logs correctly', () => { + it('Filters correctly with 1 rule', () => { logsMatchFilter(filters[0].regExp); }); it('Multiselect dialog works', () => { - widget.assertText( - '[data-cy="filters-chip"] .MuiChip-label', - filters[0].label - ); + assertChip(widget, filters[0].label) widget.click('[data-cy="filters-chip"] .MuiChip-deleteIcon'); - cy.contains( - '[data-cy="filters-chip"] .MuiChip-label', - filters[0].label - ).should('not.exist'); + assertChip(widget, filters[0].label, "not.exist") widget.click('[data-cy="filters-menu"]'); widget.click('[data-cy="filters-menu-option"]'); cy.get('[data-cy="filters-menu-option"]').type('{esc}'); - widget.assertText( - '[data-cy="filters-chip"] .MuiChip-label', - filters[0].label - ); + assertChip(widget, filters[0].label) }); - it('Editing filter works', () => { + it('Editing filter', () => { openAdvancedMenu(); widget.click('[data-cy="edit-filter-edit-button"]'); widget.assertText('h2', 'Edit filter'); @@ -116,15 +68,33 @@ describe('Logs Viewer', () => { fillFormField('reg-exp', filters[1].regExp); submitForm(); - isFilterVisibleInAdvancedMenu(filters[1].label); + isFilterVisibleInAdvancedMenu(widget, filters[1].label); closeAdvancedMenu(); logsMatchFilter(filters[1].regExp); - widget.assertText( - '[data-cy="filters-chip"] .MuiChip-label', - filters[1].label - ); + assertChip(widget, filters[1].label) }); + + it('Filters correctly with 2 rules', () => { + openAdvancedMenu(); + addFilter(filters[0]); + closeAdvancedMenu(); + + logsMatchFilter(filters[0].regExp); + logsMatchFilter(filters[1].regExp); + }) + + it('Deleting filters', () => { + openAdvancedMenu(); + + cy.get('[data-cy="delete-filter-delete-button"]').each(filter => { + cy.wrap(filter).click() + cy.get('[data-cy="confirmation-dialog-ok"]').click() + }) + + filters.forEach( filter => widget.assertText('span.MuiListItemText-primary', filter.label, 'not.exist')) + closeAdvancedMenu(); + }) }); // doesn't work diff --git a/functional/cypress-tests/cypress/support/logsViewer/filters.js b/functional/cypress-tests/cypress/support/logsViewer/filters.js new file mode 100644 index 000000000..84e3f2017 --- /dev/null +++ b/functional/cypress-tests/cypress/support/logsViewer/filters.js @@ -0,0 +1,44 @@ +export const logsMatchFilter = regExp => cy.get('[data-cy="log-entry"] ') + .each(log => + cy.wrap(log).contains( + '[data-cy="log-variable-data"] p', + new RegExp(regExp) + ) + ); + +export const openAdvancedMenu = () => + cy.get('[data-cy="advanced-filters-button"]').click() + +export const closeAdvancedMenu = () => + cy.get('[data-cy="advanced-filters-menu-exit-button"]').click() + +export const isFilterVisibleInAdvancedMenu = (widget, label) => { + widget.assertText('span.MuiListItemText-primary', label); + widget.isChecked( + '.MuiListItemSecondaryAction-root input[type="checkbox"]', + true + ); +}; + +export const assertChip = (widget, label, chainer) => { + widget.assertText( + '[data-cy="filters-chip"] .MuiChip-label', + label, chainer + ); +} + +export const fillFormField = (field, value) => { + cy.get(`[data-cy="filter-form-${field}-input"]`) + .clear() + .type(value); +}; + +export const submitForm = () => + cy.get('[data-cy="filter-form-submit-button"]').click(); + +export const addFilter = (filter) => { + cy.get('[data-cy="add-filter-add-button"]').click(); + fillFormField('label', filter.label); + fillFormField('reg-exp', filter.regExp); + submitForm(); +} \ No newline at end of file From 9e9630ff24c80a7f20b30d8bb0140db9024c0eb0 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 20 Dec 2021 02:20:25 +0100 Subject: [PATCH 123/226] Code formating #440 --- .../cypress/fixtures/logsViewer.js | 8 ++-- .../cypress/integration/logsViewer.js | 48 ++++++++++++------- .../cypress/support/logsViewer/filters.js | 32 ++++++------- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/functional/cypress-tests/cypress/fixtures/logsViewer.js b/functional/cypress-tests/cypress/fixtures/logsViewer.js index d4459fed1..ddd1c3239 100644 --- a/functional/cypress-tests/cypress/fixtures/logsViewer.js +++ b/functional/cypress-tests/cypress/fixtures/logsViewer.js @@ -1,10 +1,10 @@ export const filters = [ { label: 'starts with a', - regExp: '^a' + regExp: '^a', }, { label: 'ends with a', - regExp: 'a$' - } -]; \ No newline at end of file + regExp: 'a$', + }, +]; diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index a38d504a5..9fe091068 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -1,6 +1,15 @@ import { logsViewer } from '../fixtures/Widgets'; import { createWidget } from '../support/widget'; -import { openAdvancedMenu, closeAdvancedMenu, addFilter, isFilterVisibleInAdvancedMenu, fillFormField, logsMatchFilter, submitForm, assertChip} from '../support/logsViewer/filters'; +import { + openAdvancedMenu, + closeAdvancedMenu, + addFilter, + isFilterVisibleInAdvancedMenu, + fillFormField, + logsMatchFilter, + submitForm, + assertChip, +} from '../support/logsViewer/filters'; import { filters } from '../fixtures/logsViewer'; const dashboardName = 'Welcome to Cogboard'; @@ -16,12 +25,11 @@ describe('Logs Viewer', () => { cy.clickAddWidgetButton(); widget = createWidget(logsViewer.name).configure(false, { cols: 8, - rows: 2 + rows: 2, }); }); describe('Filters', () => { - it('Advanced filters modal', () => { openAdvancedMenu(); widget.assertText('h2', 'Advanced filters'); @@ -30,7 +38,7 @@ describe('Logs Viewer', () => { it('Adding filter', () => { openAdvancedMenu(); - addFilter(filters[0]) + addFilter(filters[0]); isFilterVisibleInAdvancedMenu(widget, filters[0].label); closeAdvancedMenu(); @@ -41,14 +49,14 @@ describe('Logs Viewer', () => { }); it('Multiselect dialog works', () => { - assertChip(widget, filters[0].label) + assertChip(widget, filters[0].label); widget.click('[data-cy="filters-chip"] .MuiChip-deleteIcon'); - assertChip(widget, filters[0].label, "not.exist") + assertChip(widget, filters[0].label, 'not.exist'); widget.click('[data-cy="filters-menu"]'); widget.click('[data-cy="filters-menu-option"]'); cy.get('[data-cy="filters-menu-option"]').type('{esc}'); - assertChip(widget, filters[0].label) + assertChip(widget, filters[0].label); }); it('Editing filter', () => { @@ -72,7 +80,7 @@ describe('Logs Viewer', () => { closeAdvancedMenu(); logsMatchFilter(filters[1].regExp); - assertChip(widget, filters[1].label) + assertChip(widget, filters[1].label); }); it('Filters correctly with 2 rules', () => { @@ -82,19 +90,25 @@ describe('Logs Viewer', () => { logsMatchFilter(filters[0].regExp); logsMatchFilter(filters[1].regExp); - }) + }); it('Deleting filters', () => { openAdvancedMenu(); - - cy.get('[data-cy="delete-filter-delete-button"]').each(filter => { - cy.wrap(filter).click() - cy.get('[data-cy="confirmation-dialog-ok"]').click() - }) - - filters.forEach( filter => widget.assertText('span.MuiListItemText-primary', filter.label, 'not.exist')) + + cy.get('[data-cy="delete-filter-delete-button"]').each((filter) => { + cy.wrap(filter).click(); + cy.get('[data-cy="confirmation-dialog-ok"]').click(); + }); + + filters.forEach((filter) => + widget.assertText( + 'span.MuiListItemText-primary', + filter.label, + 'not.exist' + ) + ); closeAdvancedMenu(); - }) + }); }); // doesn't work diff --git a/functional/cypress-tests/cypress/support/logsViewer/filters.js b/functional/cypress-tests/cypress/support/logsViewer/filters.js index 84e3f2017..14bb69914 100644 --- a/functional/cypress-tests/cypress/support/logsViewer/filters.js +++ b/functional/cypress-tests/cypress/support/logsViewer/filters.js @@ -1,16 +1,17 @@ -export const logsMatchFilter = regExp => cy.get('[data-cy="log-entry"] ') - .each(log => - cy.wrap(log).contains( - '[data-cy="log-variable-data"] p', - new RegExp(regExp) - ) - ); +export const logsMatchFilter = (regExp) => + cy + .get('[data-cy="log-entry"] ') + .each((log) => + cy + .wrap(log) + .contains('[data-cy="log-variable-data"] p', new RegExp(regExp)) + ); export const openAdvancedMenu = () => - cy.get('[data-cy="advanced-filters-button"]').click() + cy.get('[data-cy="advanced-filters-button"]').click(); export const closeAdvancedMenu = () => - cy.get('[data-cy="advanced-filters-menu-exit-button"]').click() + cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); export const isFilterVisibleInAdvancedMenu = (widget, label) => { widget.assertText('span.MuiListItemText-primary', label); @@ -21,16 +22,11 @@ export const isFilterVisibleInAdvancedMenu = (widget, label) => { }; export const assertChip = (widget, label, chainer) => { - widget.assertText( - '[data-cy="filters-chip"] .MuiChip-label', - label, chainer - ); -} + widget.assertText('[data-cy="filters-chip"] .MuiChip-label', label, chainer); +}; export const fillFormField = (field, value) => { - cy.get(`[data-cy="filter-form-${field}-input"]`) - .clear() - .type(value); + cy.get(`[data-cy="filter-form-${field}-input"]`).clear().type(value); }; export const submitForm = () => @@ -41,4 +37,4 @@ export const addFilter = (filter) => { fillFormField('label', filter.label); fillFormField('reg-exp', filter.regExp); submitForm(); -} \ No newline at end of file +}; From 632d22d39409d83ca5c598f3007cd01a2ef61f47 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 20 Dec 2021 14:24:45 +0100 Subject: [PATCH 124/226] Fix tests #440 --- functional/cypress-tests/cypress/fixtures/logsViewer.js | 4 ++-- functional/cypress-tests/cypress/integration/logsViewer.js | 1 - .../cypress-tests/cypress/support/logsViewer/filters.js | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/functional/cypress-tests/cypress/fixtures/logsViewer.js b/functional/cypress-tests/cypress/fixtures/logsViewer.js index ddd1c3239..4218e8592 100644 --- a/functional/cypress-tests/cypress/fixtures/logsViewer.js +++ b/functional/cypress-tests/cypress/fixtures/logsViewer.js @@ -4,7 +4,7 @@ export const filters = [ regExp: '^a', }, { - label: 'ends with a', - regExp: 'a$', + label: 'amet', + regExp: 'amet', }, ]; diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 9fe091068..e8eda2bad 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -80,7 +80,6 @@ describe('Logs Viewer', () => { closeAdvancedMenu(); logsMatchFilter(filters[1].regExp); - assertChip(widget, filters[1].label); }); it('Filters correctly with 2 rules', () => { diff --git a/functional/cypress-tests/cypress/support/logsViewer/filters.js b/functional/cypress-tests/cypress/support/logsViewer/filters.js index 14bb69914..84f67041e 100644 --- a/functional/cypress-tests/cypress/support/logsViewer/filters.js +++ b/functional/cypress-tests/cypress/support/logsViewer/filters.js @@ -5,6 +5,7 @@ export const logsMatchFilter = (regExp) => cy .wrap(log) .contains('[data-cy="log-variable-data"] p', new RegExp(regExp)) + .should('exist') ); export const openAdvancedMenu = () => @@ -21,7 +22,7 @@ export const isFilterVisibleInAdvancedMenu = (widget, label) => { ); }; -export const assertChip = (widget, label, chainer) => { +export const assertChip = (widget, label, chainer = 'exist') => { widget.assertText('[data-cy="filters-chip"] .MuiChip-label', label, chainer); }; From 8f7040f6cae1eed409846dd16a9683444a9f3444 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 22 Dec 2021 19:35:05 +0100 Subject: [PATCH 125/226] Rework unit tests #440 --- .../LogList/filterByRegExp.test.js | 128 ++++++++++-------- 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js index 301f87e21..37ce8700a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByRegExp.test.js @@ -5,73 +5,85 @@ const logHeader = { seq: 228, insertedOn: 1639237459, date: '2021-12-11T15:44:00', - type: 'DEBUG', - variableData: null + type: 'DEBUG' }; -it('filter by regexp', () => { - const filters = [ +const log = { + ...logHeader, + variableData: [ { - id: 'filter-3ea57f8b-6c1c-4e2f-bbc5-760aa0526e8e', - checked: true, - regExp: '^a', - label: 'starts with a' + header: 'FelixStartLevel', + description: 'No description' }, { - id: 'filter-2afee54b-8ab6-4a0c-81db-9c92e7bf5203', - checked: true, - label: 'velit', - regExp: 'velit' - }, - { - id: 'filter-b9b72673-e6ca-481d-996a-293527032764', - checked: true, - label: 'starts with F', - regExp: '^F' - }, + header: 'id velit. vel pretium. ipsum suscipit', + description: 'No message description' + } + ] +}; +const emptyLog = { + ...logHeader, + variableData: [ { - id: 'filter-c9dac795-43c6-4a57-9329-5dd1573c9bf4', - checked: true, - label: 'ends with suscipit', - regExp: 'suscipit$' + header: '', + description: '' } - ]; + ] +}; - const log = { - ...logHeader, - variableData: [ - { - header: 'FelixStartLevel', - description: 'No description' - }, - { - header: 'id velit. vel pretium. ipsum suscipit', - description: 'No message description' - } - ] - }; - const emptyLog = { - ...logHeader, - variableData: [ - { - header: '', - description: '' - } - ] - }; +const filters = { + startsWithA: { + id: 'filter-3ea57f8b-6c1c-4e2f-bbc5-760aa0526e8e', + checked: true, + regExp: '^a', + label: 'starts with a' + }, + velit: { + id: 'filter-2afee54b-8ab6-4a0c-81db-9c92e7bf5203', + checked: true, + label: 'velit', + regExp: 'velit' + }, + startsWithF: { + id: 'filter-b9b72673-e6ca-481d-996a-293527032764', + checked: true, + label: 'starts with F', + regExp: '^F' + }, + endWithSuscipit: { + id: 'filter-c9dac795-43c6-4a57-9329-5dd1573c9bf4', + checked: true, + label: 'ends with suscipit', + regExp: 'suscipit$' + } +}; + +it('shows log without filters', () => + expect(filterByRegExp(log, [])).toBeTruthy()); + +it('filters with multiple rules', () => + expect( + filterByRegExp(log, [filters.startsWithA, filters.velit]) + ).toBeFalsy()); - expect(filterByRegExp(log, [])).toEqual(true); +it('filters with multiple rules 2', () => + expect( + filterByRegExp(log, [ + filters.velit, + filters.startsWithF, + filters.endWithSuscipit + ]) + ).toBeTruthy()); - // starts with a; velit - expect(filterByRegExp(log, [filters[0], filters[1]])).toEqual(false); - // starts with a(OFF); velit +it('checks if filter is enabled', () => expect( - filterByRegExp(log, [{ ...filters[0], checked: false }, filters[1]]) - ).toEqual(true); - // velit; starts with F; ends with suscipit - expect(filterByRegExp(log, [filters[1], filters[2], filters[3]])).toEqual( - true - ); + filterByRegExp(log, [ + { ...filters.startsWithA, checked: false }, + filters.velit + ]) + ).toBeTruthy()); - expect(filterByRegExp(emptyLog, filters)).toEqual(false); -}); +it('filters empty logs', () => + expect( + filterByRegExp(emptyLog, [filters.startsWithA, filters.endWithSuscipit]) + ).toBeFalsy()); From 670a54d72f0f8d8d6f42756dacd2c666d7e9b9d1 Mon Sep 17 00:00:00 2001 From: clmrv Date: Wed, 22 Dec 2021 21:33:50 +0100 Subject: [PATCH 126/226] Update test names #440 --- .../cypress/fixtures/logsViewer.js | 8 +-- .../cypress/integration/logsViewer.js | 64 +++++++++---------- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/functional/cypress-tests/cypress/fixtures/logsViewer.js b/functional/cypress-tests/cypress/fixtures/logsViewer.js index 4218e8592..49d47c289 100644 --- a/functional/cypress-tests/cypress/fixtures/logsViewer.js +++ b/functional/cypress-tests/cypress/fixtures/logsViewer.js @@ -1,10 +1,10 @@ -export const filters = [ - { +export const filters = { + startsWithA: { label: 'starts with a', regExp: '^a', }, - { + amet: { label: 'amet', regExp: 'amet', }, -]; +}; diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index e8eda2bad..c316f6631 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -13,6 +13,8 @@ import { import { filters } from '../fixtures/logsViewer'; const dashboardName = 'Welcome to Cogboard'; +const ametFilter = filters.amet; +const startsWithAFilter = filters.startsWithA; describe('Logs Viewer', () => { let widget; @@ -30,68 +32,68 @@ describe('Logs Viewer', () => { }); describe('Filters', () => { - it('Advanced filters modal', () => { + it('opens advanced filters modal', () => { openAdvancedMenu(); widget.assertText('h2', 'Advanced filters'); closeAdvancedMenu(); }); - it('Adding filter', () => { + it('should add filter', () => { openAdvancedMenu(); - addFilter(filters[0]); + addFilter(startsWithAFilter); - isFilterVisibleInAdvancedMenu(widget, filters[0].label); + isFilterVisibleInAdvancedMenu(widget, startsWithAFilter.label); closeAdvancedMenu(); }); - it('Filters correctly with 1 rule', () => { - logsMatchFilter(filters[0].regExp); + it('filters correctly with 1 rule', () => { + logsMatchFilter(startsWithAFilter.regExp); }); - it('Multiselect dialog works', () => { - assertChip(widget, filters[0].label); + it('should select filters via multiselect dialog', () => { + assertChip(widget, startsWithAFilter.label); widget.click('[data-cy="filters-chip"] .MuiChip-deleteIcon'); - assertChip(widget, filters[0].label, 'not.exist'); + assertChip(widget, startsWithAFilter.label, 'not.exist'); widget.click('[data-cy="filters-menu"]'); widget.click('[data-cy="filters-menu-option"]'); cy.get('[data-cy="filters-menu-option"]').type('{esc}'); - assertChip(widget, filters[0].label); + assertChip(widget, startsWithAFilter.label); }); - it('Editing filter', () => { + it('should edit filter', () => { openAdvancedMenu(); widget.click('[data-cy="edit-filter-edit-button"]'); widget.assertText('h2', 'Edit filter'); + // check if form displays data to edit cy.get('[data-cy="filter-form-label-input"]').should( - 'have.attr', - 'value', - filters[0].label + 'have.value', + startsWithAFilter.label ); widget.assertText( '[data-cy="filter-form-reg-exp-input"]', - filters[0].regExp + startsWithAFilter.regExp ); - fillFormField('label', filters[1].label); - fillFormField('reg-exp', filters[1].regExp); + fillFormField('label', ametFilter.label); + fillFormField('reg-exp', ametFilter.regExp); submitForm(); - isFilterVisibleInAdvancedMenu(widget, filters[1].label); + isFilterVisibleInAdvancedMenu(widget, ametFilter.label); closeAdvancedMenu(); - logsMatchFilter(filters[1].regExp); + logsMatchFilter(ametFilter.regExp); }); - it('Filters correctly with 2 rules', () => { + it('filters correctly with 2 rules', () => { openAdvancedMenu(); - addFilter(filters[0]); + addFilter(startsWithAFilter); closeAdvancedMenu(); - logsMatchFilter(filters[0].regExp); - logsMatchFilter(filters[1].regExp); + logsMatchFilter(startsWithAFilter.regExp); + logsMatchFilter(ametFilter.regExp); }); - it('Deleting filters', () => { + it('should delete filters', () => { openAdvancedMenu(); cy.get('[data-cy="delete-filter-delete-button"]').each((filter) => { @@ -99,20 +101,14 @@ describe('Logs Viewer', () => { cy.get('[data-cy="confirmation-dialog-ok"]').click(); }); - filters.forEach((filter) => + for (const filter in filters) { widget.assertText( 'span.MuiListItemText-primary', - filter.label, + filters[filter].label, 'not.exist' - ) - ); + ); + } closeAdvancedMenu(); }); }); - - // doesn't work - - // after(() => { - // widget.remove() - // }) }); From c4142fb932f806a96bf3508748cde080b891bb45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Thu, 23 Dec 2021 00:20:24 +0100 Subject: [PATCH 127/226] Change log fetching --- .../cogboard/logStorage/LogController.kt | 49 ++++++++++++++++ .../cogboard/logStorage/LogStorage.kt | 58 +++++++++---------- .../cogboard/logStorage/{ => model}/Log.kt | 29 +--------- .../{ => model}/LogCollectionConfiguration.kt | 2 +- .../{ => model}/LogStorageConfiguration.kt | 2 +- .../logStorage/model/LogVariableData.kt | 29 ++++++++++ .../logStorage/{ => model}/QuarantineRule.kt | 2 +- .../SSHConnectionStrategy.kt | 5 ++ .../widget/type/logviewer/LogViewerWidget.kt | 6 +- .../logviewer/logparser/LogParserStrategy.kt | 2 +- .../logparser/MockLogParserStrategy.kt | 4 +- ...x.server.api.handler.RoutingHandlerFactory | 3 +- cogboard-local-compose.yml | 2 +- .../types/LogViewerWidget/LogList/index.js | 3 +- .../widgets/types/LogViewerWidget/helpers.js | 9 +++ .../widgets/types/LogViewerWidget/index.js | 21 +++++-- cogboard-webapp/src/constants/index.js | 3 +- cogboard-webapp/src/utils/fetch.js | 5 ++ knotx/conf/openapi.yaml | 13 +++++ knotx/conf/routes/operations.conf | 14 +++++ 20 files changed, 186 insertions(+), 75 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/{ => model}/Log.kt (73%) rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/{ => model}/LogCollectionConfiguration.kt (94%) rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/{ => model}/LogStorageConfiguration.kt (76%) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/{ => model}/QuarantineRule.kt (95%) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt new file mode 100644 index 000000000..0e6c489a8 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt @@ -0,0 +1,49 @@ +package com.cognifide.cogboard.logStorage + +import com.cognifide.cogboard.CogboardConstants.Props +import com.cognifide.cogboard.logStorage.model.Log +import com.cognifide.cogboard.storage.VolumeStorageFactory.boards +import com.cognifide.cogboard.widget.type.logviewer.LogViewerWidget +import com.mongodb.client.model.Sorts +import io.knotx.server.api.handler.RoutingHandlerFactory +import io.vertx.core.Handler +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject +import io.vertx.reactivex.core.Vertx +import io.vertx.reactivex.ext.web.RoutingContext + +class LogController : RoutingHandlerFactory { + + override fun getName(): String = "logs-handler" + + override fun create(vertx: Vertx?, config: JsonObject?): Handler = Handler { context -> + context?.request()?.params()?.get("id")?.let { id -> + val logs = JsonArray(getLogs(id).map { it.toJson() }) + context.response().end(logs.encode()) + } + } + + fun getLogs(id: String): List { + val logLines = boards() + .loadConfig() + ?.getJsonObject(Props.WIDGETS) + ?.getJsonObject(Props.WIDGETS_BY_ID) + ?.getJsonObject(id) + ?.getInteger(Props.LOG_LINES) + ?: LogViewerWidget.DEFAULT_LOG_LINES.toInt() + + return fetchLogs(id, logLines) + } + + private fun fetchLogs(id: String, logLines: Int): List { + return LogStorage.database + ?.getCollection(id) + ?.find() + ?.sort(Sorts.descending(Log.SEQ)) + ?.limit(logLines) + ?.mapNotNull { it } + ?.mapNotNull { Log.from(it) } + ?.sortedBy { it.seq } + ?: emptyList() + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index b3923616b..e74071a39 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -22,10 +22,11 @@ import io.vertx.core.json.JsonObject import io.vertx.core.logging.Logger import io.vertx.core.logging.LoggerFactory import org.bson.Document -import main.kotlin.com.cognifide.cogboard.logStorage.Log -import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration -import main.kotlin.com.cognifide.cogboard.logStorage.LogVariableData -import main.kotlin.com.cognifide.cogboard.logStorage.QuarantineRule +import com.cognifide.cogboard.logStorage.model.Log +import com.cognifide.cogboard.logStorage.model.LogStorageConfiguration +import com.cognifide.cogboard.logStorage.model.LogVariableData +import com.cognifide.cogboard.logStorage.model.QuarantineRule +import com.cognifide.cogboard.logStorage.model.LogCollectionConfiguration import java.net.URI import java.time.Instant @@ -155,7 +156,7 @@ class LogStorage( } /** Downloads new logs and inserts the to the database. Returns the number of inserted logs. */ - private fun downloadInsertLogs(seq: Long, skipFirstLines: Long? = null): Long { + private fun downloadInsertLogs(seq: Long, skipFirstLines: Long? = null): List { var sequence = seq val logs = connection .getLogs(skipFirstLines) @@ -169,62 +170,59 @@ class LogStorage( sequence += 1 } - logsCollection?.insertMany(logs.map { it.toDocument() }) + if (logs.isNotEmpty()) { + logsCollection?.insertMany(logs.map { it.toDocument() }) + } - return logs.size.toLong() + return logs } /** Checks how many logs to download, downloads them and saves them to the database. */ - private fun downloadLogs() { + private fun downloadLogs(): List { // Get the current settings val storageConfig = collectionConfiguration var lastLine = storageConfig?.lastLine ?: 0 var seq = storageConfig?.seq ?: 0 + var inserted: List = emptyList() // Get the number of lines in the file val fileLineCount = connection.getNumberOfLines() ?: 0 if (fileLineCount > 0 && fileLineCount > lastLine) { // Download new logs and append them - val inserted = downloadInsertLogs(seq, lastLine) - lastLine += inserted - seq += inserted + inserted = downloadInsertLogs(seq, lastLine) + lastLine += inserted.size + seq += inserted.size } else if (fileLineCount in 1 until lastLine) { // Remove all logs and download from the beginning deleteAllLogs() seq = 0 - val inserted = downloadInsertLogs(seq) - lastLine = inserted - seq += inserted + inserted = downloadInsertLogs(seq) + lastLine = inserted.size.toLong() + seq += inserted.size } // Save the new configuration saveConfiguration(LogCollectionConfiguration(config.id, lastLine, seq)) + + return inserted } - /** Returns all logs for this instance, sorted by their sequence number. */ - private val logs: List - get() = logsCollection - ?.find() - ?.limit(config.logLines.toInt()) - ?.sort(descending(Log.SEQ)) - ?.mapNotNull { it } - ?.mapNotNull { Log.from(it) } - ?: emptyList() - - /** Prepares a JSON response to be displayed */ - private fun prepareResponse(): JsonObject { + /** Prepares a JSON response to be displayed. */ + private fun prepareResponse(insertedLogs: List): JsonObject { return JsonObject(mapOf( "variableFields" to parserStrategy.variableFields, - "logs" to JsonArray(logs.map { it.toJson() }) + "logs" to JsonArray(insertedLogs.map { it.toJson() }) )) } /** Updates the logs and sends them to the widget. */ fun updateLogs(fetchNewLogs: Boolean) { + var insertedLogs: List = emptyList() + if (fetchNewLogs) { // Download new logs - downloadLogs() + insertedLogs = downloadLogs() // Delete unnecessary logs deleteOldLogs() @@ -232,7 +230,7 @@ class LogStorage( } // Fetch the logs from the database and send them back - val response = prepareResponse() + val response = prepareResponse(insertedLogs) vertx?.eventBus()?.send(config.eventBusAddress, response) } @@ -281,7 +279,7 @@ class LogStorage( } /** Returns a database for storing logs and collection configurations. */ - private val database: MongoDatabase? + val database: MongoDatabase? get() = client?.getDatabase(DATABASE_NAME) /** Returns a configuration collection. */ diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt similarity index 73% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt index 25e547691..d9ec34a06 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt @@ -1,38 +1,13 @@ -package main.kotlin.com.cognifide.cogboard.logStorage +package com.cognifide.cogboard.logStorage.model +import io.vertx.core.json.JsonObject import org.bson.Document import org.bson.types.ObjectId -import io.vertx.core.json.JsonObject import java.time.Instant import java.time.LocalDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -data class LogVariableData( - val header: String, - var description: String -) { - private val map: Map - get() = mapOf( - HEADER to header, - DESCRIPTION to description) - - fun toDocument() = Document(map) - fun toJson() = JsonObject(map) - - companion object { - const val HEADER = "header" - const val DESCRIPTION = "description" - - fun from(document: Document): LogVariableData? { - val header = document.getString(HEADER) ?: return null - val description = document.getString(DESCRIPTION) ?: return null - - return LogVariableData(header, description) - } - } -} - data class Log( var id: ObjectId = ObjectId(), var seq: Long = 0, diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt similarity index 94% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt index bb58ea5a8..f851da02b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogCollectionConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt @@ -1,4 +1,4 @@ -package com.cognifide.cogboard.logStorage +package com.cognifide.cogboard.logStorage.model import org.bson.Document diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogStorageConfiguration.kt similarity index 76% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogStorageConfiguration.kt index 794b1a8de..7e3b2ec13 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorageConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogStorageConfiguration.kt @@ -1,4 +1,4 @@ -package main.kotlin.com.cognifide.cogboard.logStorage +package com.cognifide.cogboard.logStorage.model data class LogStorageConfiguration( val id: String, diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt new file mode 100644 index 000000000..f285fd815 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt @@ -0,0 +1,29 @@ +package com.cognifide.cogboard.logStorage.model + +import io.vertx.core.json.JsonObject +import org.bson.Document + +data class LogVariableData( + val header: String, + var description: String +) { + private val map: Map + get() = mapOf( + HEADER to header, + DESCRIPTION to description) + + fun toDocument() = Document(map) + fun toJson() = JsonObject(map) + + companion object { + const val HEADER = "header" + const val DESCRIPTION = "description" + + fun from(document: Document): LogVariableData? { + val header = document.getString(HEADER) ?: return null + val description = document.getString(DESCRIPTION) ?: return null + + return LogVariableData(header, description) + } + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/QuarantineRule.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt similarity index 95% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/QuarantineRule.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt index b34c6bf54..92773d9cc 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/QuarantineRule.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt @@ -1,4 +1,4 @@ -package main.kotlin.com.cognifide.cogboard.logStorage +package com.cognifide.cogboard.logStorage.model import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt index 648e1e4e5..4d5d924dd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/connectionStrategy/SSHConnectionStrategy.kt @@ -4,9 +4,14 @@ import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.ssh.SSHClient import io.vertx.core.json.Json import io.vertx.core.json.JsonObject +import com.cognifide.cogboard.ssh.auth.AuthenticationType class SSHConnectionStrategy(val config: JsonObject) : ConnectionStrategy() { + override fun authenticationTypes(): Set { + return setOf(AuthenticationType.BASIC, AuthenticationType.SSH_KEY) + } + override fun getNumberOfLines(): Long? { val logFilePath = config.getString(Props.PATH) ?: return null diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index e01cdbe1c..d0cfdfc62 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -14,8 +14,8 @@ import io.vertx.core.Vertx import io.vertx.core.eventbus.MessageConsumer import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject -import main.kotlin.com.cognifide.cogboard.logStorage.LogStorageConfiguration -import main.kotlin.com.cognifide.cogboard.logStorage.QuarantineRule +import com.cognifide.cogboard.logStorage.model.LogStorageConfiguration +import com.cognifide.cogboard.logStorage.model.QuarantineRule class LogViewerWidget( vertx: Vertx, @@ -117,7 +117,7 @@ class LogViewerWidget( companion object { private const val DEFAULT_ID = "0" - private const val DEFAULT_LOG_LINES = 100L + const val DEFAULT_LOG_LINES = 1000L private const val DEFAULT_LOG_FILE_SIZE = 50L private const val DEFAULT_LOG_EXPIRATION_DAYS = 5L diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt index ba4fc133c..94c2dfd3f 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt @@ -1,6 +1,6 @@ package com.cognifide.cogboard.widget.type.logviewer.logparser -import main.kotlin.com.cognifide.cogboard.logStorage.Log +import com.cognifide.cogboard.logStorage.model.Log interface LogParserStrategy { val variableFields: List diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt index c90b116aa..fb126323c 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt @@ -1,7 +1,7 @@ package com.cognifide.cogboard.widget.type.logviewer.logparser -import main.kotlin.com.cognifide.cogboard.logStorage.Log -import main.kotlin.com.cognifide.cogboard.logStorage.LogVariableData +import com.cognifide.cogboard.logStorage.model.Log +import com.cognifide.cogboard.logStorage.model.LogVariableData import java.time.LocalDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter diff --git a/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory b/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory index a41fb2c60..1d7c36261 100644 --- a/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory +++ b/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory @@ -4,4 +4,5 @@ com.cognifide.cogboard.widget.handler.UpdateWidget com.cognifide.cogboard.widget.handler.DeleteWidget com.cognifide.cogboard.widget.handler.ContentUpdateWidget com.cognifide.cogboard.security.LoginHandler -com.cognifide.cogboard.security.SessionHandler \ No newline at end of file +com.cognifide.cogboard.security.SessionHandler +com.cognifide.cogboard.logStorage.LogController \ No newline at end of file diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 08727bc5f..329c2d671 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -38,7 +38,7 @@ services: # - "18092:18092" mongo: - image: mongo + image: mongo:4 restart: always environment: MONGO_INITDB_ROOT_USERNAME: "root" diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index dbda96ba2..7e8022ed2 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -39,8 +39,7 @@ export default function LogList({ const filteredLogs = logs ?.filter(log => filterByRegExp(log, filters)) .filter(log => filterByDateSpan(log, dateSpan)) - .filter(log => filterByLevel(log, level)) - .reverse(); // maybe logs can be sent in correct order + .filter(log => filterByLevel(log, level)); const VariableLogListHeader = () => ( diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js new file mode 100644 index 000000000..f27fec341 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js @@ -0,0 +1,9 @@ +export const joinLogs = (currentLogs, newLogs, logLines) => { + let joined = currentLogs; + newLogs.forEach(newLog => { + if (!joined.some(log => log._id === newLog._id)) { + joined.push(newLog); + } + }); + return joined.slice(-logLines); +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 649c2b240..903ea7a74 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import { number, string } from 'prop-types'; import { useLocalStorage } from '../../../../hooks'; @@ -6,6 +6,8 @@ import { useLocalStorage } from '../../../../hooks'; import Toolbar from './Toolbar'; import LogList from './LogList'; import { Container } from './styled'; +import { getInitialLogs } from '../../../../utils/fetch'; +import { joinLogs } from './helpers'; const LogViewerWidget = ({ id }) => { const widgetData = useSelector( @@ -23,9 +25,20 @@ const LogViewerWidget = ({ id }) => { const [searchFilter, setSearchFilter] = useState(''); const [shouldFollowLogs, setFollow] = useState(true); - const logs = widgetData.content?.logs; + const storedLogs = useRef([]); + + useEffect(() => { + getInitialLogs(id).then(logs => { + storedLogs.current = logs; + }); + }, [id]); + + const newLogs = widgetData.content?.logs || []; const template = widgetData.content?.variableFields; const quarantine = widgetData.content?.quarantineRules || []; + const logLines = widgetData.logLinesField || 1000; + + storedLogs.current = joinLogs(storedLogs.current, newLogs, logLines); return ( @@ -37,10 +50,10 @@ const LogViewerWidget = ({ id }) => { shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} /> - {logs && ( + {storedLogs.current && ( { const { status, statusText } = response; @@ -26,3 +27,7 @@ export const postWidgetContentUpdate = (data = {}) => { .then(checkResponseStatus) .then(response => response.json()); }; + +export const getInitialLogs = id => { + return fetch(`${URL.LOGS_ENDPOINT}/${id}`).then(response => response.json()); +}; diff --git a/knotx/conf/openapi.yaml b/knotx/conf/openapi.yaml index f04d96eb8..a6b721f76 100644 --- a/knotx/conf/openapi.yaml +++ b/knotx/conf/openapi.yaml @@ -185,6 +185,19 @@ paths: responses: default: description: Widget Content Refresh Handler + /api/logs/{id}: + parameters: + - name: id + in: path + description: id of the widget for which to get logs + required: true + schema: + type: string + get: + operationId: logs-get-handler + responses: + default: + description: Initial Logs Handler components: securitySchemes: cogboardAuth: diff --git a/knotx/conf/routes/operations.conf b/knotx/conf/routes/operations.conf index aff63a164..d1d715187 100644 --- a/knotx/conf/routes/operations.conf +++ b/knotx/conf/routes/operations.conf @@ -249,6 +249,20 @@ routingOperations = ${routingOperations} [ } ] } + { + operationId = logs-get-handler + handlers = [ + { + name = logs-handler + config { + address = cogboard.logs.get + method = get + payload = params + refresh = false + } + } + ] + } { operationId = version-handler handlers = [{ name = version-handler }] From d91d200c4fbe5a8996447d1dbd153b52fce0e1df Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 23 Dec 2021 00:21:33 +0100 Subject: [PATCH 128/226] Refactor log levels #441 --- .../types/LogViewerWidget/LogList/helpers.js | 10 +++------- .../widgets/types/LogViewerWidget/LogList/styled.js | 5 +---- .../LogViewerWidget/Toolbar/FilterPicker/index.js | 6 +++--- .../widgets/types/LogViewerWidget/logLevels.js | 13 ++++++------- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 30339da45..605ff0318 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -8,13 +8,9 @@ export const getGridTemplate = columnNames => { }; export const filterByLevel = (log, level) => { - const lowestLevel = logLevels.find( - elem => elem.value.toLowerCase() === level.toLowerCase() - ); - const logLevel = logLevels.find( - elem => elem.value.toLowerCase() === log.type.toLowerCase() - ); - return logLevel.level >= lowestLevel.level; + const logLevel = logLevels[log.type.toLowerCase()]?.level; + const selectedLevel = logLevels[level.toLowerCase()].level; + return logLevel ? logLevel >= selectedLevel : true; }; const getLogTexts = log => { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index e92742e43..605c07213 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -41,12 +41,9 @@ export const ColumnTitle = styled(Typography)` export const Text = styled(Typography)(({ type }) => { let logTypeStyles = ``; if (type) { - const logLevel = logLevels.find( - level => level.value === type?.toLowerCase() - ); logTypeStyles = ` font-weight: 500; - color: ${logLevel?.color || COLORS.WHITE}; + color: ${logLevels[type.toLowerCase()]?.color || COLORS.WHITE}; `; } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index bdc99d573..b2b0d6b79 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -90,9 +90,9 @@ const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { value={logLevel} onChange={e => handleLevelSelection(e.target.value)} > - {logLevels.map((level, index) => ( - - {level.value.toUpperCase()} + {Object.keys(logLevels).map((key, index) => ( + + {key.toUpperCase()} ))} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js index 1c6f2682a..ed7e7183e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels.js @@ -1,11 +1,10 @@ import { COLORS } from '../../../../constants'; -const logLevels = [ - { value: 'debug', color: COLORS.WHITE, level: 0 }, - { value: 'info', color: COLORS.WHITE, level: 1 }, - { value: 'warning', color: COLORS.YELLOW, level: 2 }, - { value: 'error', color: COLORS.RED, level: 3 }, - { value: 'success', color: COLORS.GREEN, level: 4 } -]; +const logLevels = { + debug: { level: 10, color: COLORS.WHITE }, + info: { level: 20, color: COLORS.WHITE }, + warning: { level: 30, color: COLORS.YELLOW }, + error: { level: 40, color: COLORS.RED } +}; export default logLevels; From 4ff1c33739be27e47cd59d27ebf0a08d7470c5b6 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 23 Dec 2021 00:21:53 +0100 Subject: [PATCH 129/226] Add logLevel unit tests #441 --- .../LogList/filterByLevel.test.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByLevel.test.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByLevel.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByLevel.test.js new file mode 100644 index 000000000..184d6c0e4 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByLevel.test.js @@ -0,0 +1,21 @@ +import { filterByLevel } from './helpers'; + +const log = { + _id: '61b4c753bc103d391657c49c', + seq: 228, + insertedOn: 1639237459, + date: '2021-12-11T15:44:00', + variableData: [] +}; + +it('lets through log with unknown level', () => + expect(filterByLevel({ ...log, type: 'smth' }, 'error')).toBeTruthy()); + +it('is not case sensitive', () => + expect(filterByLevel({ ...log, type: 'eRroR' }, 'ErROr')).toBeTruthy()); + +it('lets through logs with greater level', () => + expect(filterByLevel({ ...log, type: 'error' }, 'info')).toBeTruthy()); + +it('filters out logs with lower level', () => + expect(filterByLevel({ ...log, type: 'info' }, 'warning')).toBeFalsy()); From 475b52645b4e054fe614ee70d68a10aa50cd5b47 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 23 Dec 2021 00:37:13 +0100 Subject: [PATCH 130/226] Update filtering sequence #441 --- .../components/widgets/types/LogViewerWidget/LogList/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index dbda96ba2..a851424c7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -37,9 +37,9 @@ export default function LogList({ const dateSpan = getDateSpan(widgetLocalStorage); const filteredLogs = logs - ?.filter(log => filterByRegExp(log, filters)) + ?.filter(log => filterByLevel(log, level)) .filter(log => filterByDateSpan(log, dateSpan)) - .filter(log => filterByLevel(log, level)) + .filter(log => filterByRegExp(log, filters)) .reverse(); // maybe logs can be sent in correct order const VariableLogListHeader = () => ( From 8baebfe289c611ff71dd72cc7317d6501147ecbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Thu, 23 Dec 2021 01:20:19 +0100 Subject: [PATCH 131/226] Fix bugs after merge --- .../cogboard/widget/type/logviewer/LogViewerWidget.kt | 4 +++- .../type/logviewer/logparser/LogParserStrategyFactory.kt | 2 +- .../widgets/types/LogViewerWidget/Toolbar/index.js | 4 ++-- .../src/components/widgets/types/LogViewerWidget/index.js | 5 ++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 25f4d627c..68b803d88 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -113,7 +113,9 @@ class LogViewerWidget( private fun determineLogParsingStrategy() = LogParserStrategyFactory() - .build(config.getString(Props.LOG_PARSER)) + .build(config.getString(Props.LOG_PARSER) + ?: LogParserStrategyFactory.Type.MOCK.toString() + ) companion object { private const val DEFAULT_ID = "0" diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt index ceb4c498b..6f2b2b744 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt @@ -10,7 +10,7 @@ class LogParserStrategyFactory { when (Type.valueOf(typeStr.toUpperCase())) { Type.MOCK -> MockLogParserStrategy() } - } catch (e : IllegalArgumentException) { + } catch (e: IllegalArgumentException) { throw UnknownParserTypeException("Unknown log parsing type") } } diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 823411615..f2f4d2a1d 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -18,12 +18,12 @@ const Toolbar = ({ setSearchFilter, shouldFollowLogs, handleFollowChange, - firstLog + lastLog }) => { const theme = useTheme(); const handleClearLogs = () => { - const date = firstLog?.date; + const date = lastLog?.date; if (date) { const beginDate = moment(date).add(1, 'seconds'); saveDateSpan(widgetLocalStorage, { begin: beginDate, end: null }); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 7bb0eb21c..0082a3f62 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -49,7 +49,10 @@ const LogViewerWidget = ({ id }) => { setSearchFilter={setSearchFilter} shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} - firstLog={logs && logs[0]} + lastLog={ + storedLogs.current && + storedLogs.current[storedLogs.current.length - 1] + } /> {storedLogs.current && ( Date: Thu, 23 Dec 2021 01:35:02 +0100 Subject: [PATCH 132/226] Add filterByDateSpan unit tests #441 --- .../LogList/filterByDateSpan.test.js | 68 +++++++++++++++++++ .../types/LogViewerWidget/LogList/helpers.js | 9 +++ 2 files changed, 77 insertions(+) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js new file mode 100644 index 000000000..89539c9f2 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js @@ -0,0 +1,68 @@ +import { filterByDateSpan } from './helpers'; +import moment from 'moment-timezone'; + +const log = { + _id: '61b4c753bc103d391657c49c', + seq: 228, + insertedOn: 1639237459, + type: 'info', + variableData: [] +}; + +const spanTime10 = moment('2021-12-22T10:00:00.000Z'); +const logTime15 = '2021-12-22T15:00:00.000'; +const spanTime19 = moment('2021-12-22T19:00:00.000Z'); +const spanTime20 = moment('2021-12-22T20:00:00.000Z'); + +it('filters out log without date', () => + expect( + filterByDateSpan(log, { begin: spanTime10, end: spanTime20 }) + ).toBeFalsy()); + +it('should be truthy when no date span is provided', () => + expect(filterByDateSpan(log, { begin: null, end: null })).toBeTruthy()); + +it('filters by begin date', () => { + expect( + filterByDateSpan( + { ...log, date: logTime15 }, + { begin: spanTime10, end: null } + ) + ).toBeTruthy(); + expect( + filterByDateSpan( + { ...log, date: '2021-12-22T20:00:00' }, + { begin: spanTime20, end: null } + ) + ).toBeFalsy(); +}); + +it('filters by end date', () => { + expect( + filterByDateSpan( + { ...log, date: logTime15 }, + { begin: null, end: spanTime20 } + ) + ).toBeTruthy(); + expect( + filterByDateSpan( + { ...log, date: logTime15 }, + { begin: null, end: spanTime10 } + ) + ).toBeFalsy(); +}); + +it('filters by date span', () => { + expect( + filterByDateSpan( + { ...log, date: logTime15 }, + { begin: spanTime10, end: spanTime20 } + ) + ).toBeTruthy(); + expect( + filterByDateSpan( + { ...log, date: logTime15 }, + { begin: spanTime19, end: spanTime20 } + ) + ).toBeFalsy(); +}); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 605ff0318..5414b3b2b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -46,7 +46,16 @@ export const filterByRegExp = (log, filters) => getLogTexts(log).some(text => text.match(new RegExp(regExp))) ); +/* + log: string? + begin: momentjs-object? + end: momentjs-object? +*/ export const filterByDateSpan = (log, { begin, end }) => { + if (!log.date) { + return !begin && !end; // let empty date through if there is no date span + } + const date = new Date(log.date); if (begin && date < begin) return false; From 1d9439a60e62f0a7a209a3435f7cc872bb90b24b Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 23 Dec 2021 01:44:16 +0100 Subject: [PATCH 133/226] Add info about dates storage --- .../components/widgets/types/LogViewerWidget/LogsViewer.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index abb14d6d9..e02e20afe 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -23,6 +23,10 @@ Uses local storage of the browser. Uses local storage of the browser and `momentjs` library. Ignores seconds. +Dates (begin and end) are held in state as `momentjs` objects. Each time value changes, it is saved as `string` in browser local storage. When it is loaded from local storage(eg. Logs Viewer reload) it is converted back to `momentjs` object. + +Log dates are strings. + ### Follow logs _TODO_ From cdc9462f0b65102e6a2e875f6cce58f2416d91b9 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 23 Dec 2021 11:28:18 +0100 Subject: [PATCH 134/226] Add getDateSpan unit tests #441 --- .../DateRangePicker/getDateSpan.test.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/getDateSpan.test.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/getDateSpan.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/getDateSpan.test.js new file mode 100644 index 000000000..e28df5b0c --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/getDateSpan.test.js @@ -0,0 +1,22 @@ +import moment from 'moment-timezone'; +import { getDateSpan } from './helpers'; + +const time20 = '2021-12-22T20:00:00.000Z'; +const time22 = '2021-12-22T22:00:00.000Z'; + +const widgetLocalStorage = { + get: () => ({ dateSpan: { begin: time20, end: time22 } }) +}; +const emptyWidgetLocalStorage = { get: () => ({}) }; + +it('returns dates as momentjs object', () => + expect(getDateSpan(widgetLocalStorage)).toEqual({ + begin: moment(time20), + end: moment(time22) + })); + +it('returns null when no date defined', () => + expect(getDateSpan(emptyWidgetLocalStorage)).toEqual({ + begin: null, + end: null + })); From 6b47f5553140949c35f4bde67e94acc13aa7278a Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 23 Dec 2021 11:36:35 +0100 Subject: [PATCH 135/226] Update useLocalStorage naming #441 --- cogboard-webapp/src/hooks/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cogboard-webapp/src/hooks/index.js b/cogboard-webapp/src/hooks/index.js index d5f0b6d69..f9855f362 100644 --- a/cogboard-webapp/src/hooks/index.js +++ b/cogboard-webapp/src/hooks/index.js @@ -138,10 +138,9 @@ export function useEventListener(eventName, handler, element = window) { } export const useLocalStorage = key => { - const localStorage = window.localStorage.getItem(key); - const [data, setStoredValue] = useState( - localStorage ? JSON.parse(localStorage) : null - ); + const stringValue = window.localStorage.getItem(key); + const objectValue = stringValue ? JSON.parse(stringValue) : null; + const [data, setStoredValue] = useState(objectValue); const setData = data => { window.localStorage.setItem(key, JSON.stringify(data)); From 6c7480784c8710eb40c930df3708e0a98608485e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Mon, 27 Dec 2021 22:32:23 +0100 Subject: [PATCH 136/226] Fix fetching the logs --- .../cogboard/logStorage/LogStorage.kt | 39 +++++++++++-------- .../widgets/types/LogViewerWidget/index.js | 22 ++++++----- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index fd9100cd2..d68c1cd93 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -13,7 +13,6 @@ import com.mongodb.client.model.Filters.`in` import com.mongodb.client.model.Filters.regex import com.mongodb.client.model.Filters.or import com.mongodb.client.model.Indexes -import com.mongodb.client.model.Sorts.descending import com.mongodb.client.model.ReplaceOptions import com.mongodb.client.model.Sorts.ascending import io.vertx.core.AbstractVerticle @@ -155,9 +154,8 @@ class LogStorage( collection.deleteMany(or(regexes)) } - /** Downloads new logs and inserts the to the database. Returns the number of inserted logs. */ - private fun downloadInsertLogs(seq: Long, skipFirstLines: Long? = null): List { - var sequence = seq + /** Downloads new logs and filters them by quarantine rules. */ + private fun downloadFilterLogs(skipFirstLines: Long? = null): List { val logs = connection .getLogs(skipFirstLines) .mapNotNull { parserStrategy.parseLine(it) } @@ -165,16 +163,22 @@ class LogStorage( // Filter the logs by quarantine rules filter(logs) + + return logs + } + + /** Inserts the logs to the database. */ + private fun insertLogs(seq: Long, logs: Collection) { + if (logs.isEmpty()) return + + var sequence = seq + logs.forEach { it.seq = sequence sequence += 1 } - if (logs.isNotEmpty()) { - logsCollection?.insertMany(logs.map { it.toDocument() }) - } - - return logs + logsCollection?.insertMany(logs.map { it.toDocument() }) } /** Checks how many logs to download, downloads them and saves them to the database. */ @@ -183,29 +187,30 @@ class LogStorage( val storageConfig = collectionConfiguration var lastLine = storageConfig?.lastLine ?: 0 var seq = storageConfig?.seq ?: 0 - var inserted: List = emptyList() + var newLogs: List = emptyList() // Get the number of lines in the file val fileLineCount = connection.getNumberOfLines() ?: 0 if (fileLineCount > 0 && fileLineCount > lastLine) { // Download new logs and append them - inserted = downloadInsertLogs(seq, lastLine) - lastLine += inserted.size - seq += inserted.size + newLogs = downloadFilterLogs(lastLine) } else if (fileLineCount in 1 until lastLine) { // Remove all logs and download from the beginning deleteAllLogs() seq = 0 - inserted = downloadInsertLogs(seq) - lastLine = inserted.size.toLong() - seq += inserted.size + lastLine = 0 + newLogs = downloadFilterLogs() } + insertLogs(seq, newLogs) + lastLine += newLogs.size + seq += newLogs.size + // Save the new configuration saveConfiguration(LogCollectionConfiguration(config.id, lastLine, seq)) - return inserted + return newLogs } /** Prepares a JSON response to be displayed. */ diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 0082a3f62..e7680d630 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useState } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import { number, string } from 'prop-types'; import { useLocalStorage } from '../../../../hooks'; @@ -25,11 +25,9 @@ const LogViewerWidget = ({ id }) => { const [searchFilter, setSearchFilter] = useState(''); const [shouldFollowLogs, setFollow] = useState(true); - const storedLogs = useRef([]); - useEffect(() => { getInitialLogs(id).then(logs => { - storedLogs.current = logs; + setStoredLogs(logs); }); }, [id]); @@ -38,7 +36,12 @@ const LogViewerWidget = ({ id }) => { const quarantine = widgetData.content?.quarantineRules || []; const logLines = widgetData.logLinesField || 1000; - storedLogs.current = joinLogs(storedLogs.current, newLogs, logLines); + const [storedLogs, setStoredLogs] = useState([]); + + useEffect(() => { + setStoredLogs(joinLogs(storedLogs, newLogs, logLines)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [widgetData]); return ( @@ -50,14 +53,15 @@ const LogViewerWidget = ({ id }) => { shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} lastLog={ - storedLogs.current && - storedLogs.current[storedLogs.current.length - 1] + storedLogs && + storedLogs.length > 0 && + storedLogs[storedLogs.length - 1] } /> - {storedLogs.current && ( + {storedLogs && ( Date: Tue, 28 Dec 2021 23:50:38 +0100 Subject: [PATCH 137/226] Fix bugs and improve the code --- .../cogboard/logStorage/LogController.kt | 4 +- .../cogboard/logStorage/LogStorage.kt | 61 ++++++++----------- .../cogboard/logStorage/model/Log.kt | 30 +++++---- ...Configuration.kt => LogCollectionState.kt} | 22 +++---- .../logStorage/model/LogVariableData.kt | 12 ++-- .../widget/type/logviewer/LogViewerWidget.kt | 4 +- .../logparser/MockLogParserStrategy.kt | 15 ++++- .../logparser/MockLogParserStrategyTest.kt | 2 +- 8 files changed, 74 insertions(+), 76 deletions(-) rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/{LogCollectionConfiguration.kt => LogCollectionState.kt} (51%) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt index 0e6c489a8..f0a66b323 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt @@ -2,6 +2,7 @@ package com.cognifide.cogboard.logStorage import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.logStorage.model.Log +import com.cognifide.cogboard.logStorage.model.asLog import com.cognifide.cogboard.storage.VolumeStorageFactory.boards import com.cognifide.cogboard.widget.type.logviewer.LogViewerWidget import com.mongodb.client.model.Sorts @@ -41,8 +42,7 @@ class LogController : RoutingHandlerFactory { ?.find() ?.sort(Sorts.descending(Log.SEQ)) ?.limit(logLines) - ?.mapNotNull { it } - ?.mapNotNull { Log.from(it) } + ?.mapNotNull { it?.asLog() } ?.sortedBy { it.seq } ?: emptyList() } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index d68c1cd93..96c3ed653 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -1,5 +1,11 @@ package com.cognifide.cogboard.logStorage +import com.cognifide.cogboard.logStorage.model.Log +import com.cognifide.cogboard.logStorage.model.LogCollectionState +import com.cognifide.cogboard.logStorage.model.asLogCollectionState +import com.cognifide.cogboard.logStorage.model.LogStorageConfiguration +import com.cognifide.cogboard.logStorage.model.LogVariableData +import com.cognifide.cogboard.logStorage.model.QuarantineRule import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategy import com.mongodb.client.MongoClient @@ -21,11 +27,6 @@ import io.vertx.core.json.JsonObject import io.vertx.core.logging.Logger import io.vertx.core.logging.LoggerFactory import org.bson.Document -import com.cognifide.cogboard.logStorage.model.Log -import com.cognifide.cogboard.logStorage.model.LogStorageConfiguration -import com.cognifide.cogboard.logStorage.model.LogVariableData -import com.cognifide.cogboard.logStorage.model.QuarantineRule -import com.cognifide.cogboard.logStorage.model.LogCollectionConfiguration import java.net.URI import java.time.Instant @@ -52,24 +53,24 @@ class LogStorage( // Storage configuration /** Returns a logs collection configuration associated with this widget (if present). */ - private val collectionConfiguration: LogCollectionConfiguration? - get() = configCollection + private val collectionState: LogCollectionState? + get() = stateCollection ?.find(eq(Log.ID, config.id)) ?.first() - ?.let { LogCollectionConfiguration.from(it) } + ?.let { it.asLogCollectionState() } - /** Saves or deletes a logs collection [configuration] associated with this widget. */ - private fun saveConfiguration(configuration: LogCollectionConfiguration?) { - if (configuration != null) { + /** Saves or deletes a logs collection [state] associated with this widget. */ + private fun saveState(state: LogCollectionState?) { + if (state != null) { val options = ReplaceOptions().upsert(true) - configCollection + stateCollection ?.replaceOne( - eq(LogCollectionConfiguration.ID, config.id), - configuration.toDocument(), + eq(LogCollectionState.ID, config.id), + state.toDocument(), options ) } else { - configCollection?.deleteOne((eq(LogCollectionConfiguration.ID, config.id))) + stateCollection?.deleteOne((eq(LogCollectionState.ID, config.id))) } } @@ -161,7 +162,6 @@ class LogStorage( .mapNotNull { parserStrategy.parseLine(it) } .toMutableList() - // Filter the logs by quarantine rules filter(logs) return logs @@ -175,7 +175,7 @@ class LogStorage( logs.forEach { it.seq = sequence - sequence += 1 + sequence++ } logsCollection?.insertMany(logs.map { it.toDocument() }) @@ -183,20 +183,15 @@ class LogStorage( /** Checks how many logs to download, downloads them and saves them to the database. */ private fun downloadLogs(): List { - // Get the current settings - val storageConfig = collectionConfiguration - var lastLine = storageConfig?.lastLine ?: 0 - var seq = storageConfig?.seq ?: 0 + var lastLine = collectionState?.lastLine ?: 0 + var seq = collectionState?.seq ?: 0 var newLogs: List = emptyList() - // Get the number of lines in the file val fileLineCount = connection.getNumberOfLines() ?: 0 if (fileLineCount > 0 && fileLineCount > lastLine) { - // Download new logs and append them newLogs = downloadFilterLogs(lastLine) } else if (fileLineCount in 1 until lastLine) { - // Remove all logs and download from the beginning deleteAllLogs() seq = 0 lastLine = 0 @@ -207,8 +202,7 @@ class LogStorage( lastLine += newLogs.size seq += newLogs.size - // Save the new configuration - saveConfiguration(LogCollectionConfiguration(config.id, lastLine, seq)) + saveState(LogCollectionState(config.id, lastLine, seq)) return newLogs } @@ -226,15 +220,12 @@ class LogStorage( var insertedLogs: List = emptyList() if (fetchNewLogs) { - // Download new logs insertedLogs = downloadLogs() - // Delete unnecessary logs deleteOldLogs() deleteSpaceConsumingLogs() } - // Fetch the logs from the database and send them back val response = prepareResponse(insertedLogs) vertx?.eventBus()?.send(config.eventBusAddress, response) } @@ -242,12 +233,12 @@ class LogStorage( /** Deletes all data associated with the widget. */ fun delete() { deleteAllLogs() - saveConfiguration(null) + saveState(null) } companion object { private const val DATABASE_NAME: String = "logs" - private const val CONFIGURATION_COLLECTION_NAME: String = "config" + private const val STATE_COLLECTION_NAME: String = "config" private const val STATS_COMMAND: String = "collStats" private const val STATS_SIZE: String = "size" private const val MB_TO_BYTES: Long = 1024L * 1024L @@ -283,13 +274,13 @@ class LogStorage( return null } - /** Returns a database for storing logs and collection configurations. */ + /** Returns a database for storing logs and collection states. */ val database: MongoDatabase? get() = client?.getDatabase(DATABASE_NAME) - /** Returns a configuration collection. */ - val configCollection: MongoCollection? - get() = database?.getCollection(CONFIGURATION_COLLECTION_NAME) + /** Returns a state collection. */ + val stateCollection: MongoCollection? + get() = database?.getCollection(STATE_COLLECTION_NAME) private val LOGGER: Logger = LoggerFactory.getLogger(LogStorage::class.java) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt index d9ec34a06..f6c30b4d8 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt @@ -42,23 +42,21 @@ data class Log( const val DATE = "date" const val TYPE = "type" const val VARIABLE_DATA = "variableData" + } +} - fun from(document: Document): Log? { - return try { - val id = document.getObjectId(ID)!! - val seq = document.getLong(SEQ)!! - val insertedOn = document.getLong(INSERTED_ON)!! - val date = document.getLong(DATE)!! - val type = document.getString(TYPE)!! +fun Document.asLog(): Log? { + return try { + val id = getObjectId(Log.ID)!! + val seq = getLong(Log.SEQ)!! + val insertedOn = getLong(Log.INSERTED_ON)!! + val date = getLong(Log.DATE)!! + val type = getString(Log.TYPE)!! - val variableData = document - .getList(VARIABLE_DATA, Document::class.java) - ?.mapNotNull { it } - ?.mapNotNull { LogVariableData.from(it) } ?: listOf() - Log(id, seq, insertedOn, date, type, variableData) - } catch (_: NullPointerException) { - null - } - } + val variableData = getList(Log.VARIABLE_DATA, Document::class.java) + ?.mapNotNull { it?.asLogVariableData() } ?: listOf() + Log(id, seq, insertedOn, date, type, variableData) + } catch (_: NullPointerException) { + null } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt similarity index 51% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt index f851da02b..22c68b798 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt @@ -2,7 +2,7 @@ package com.cognifide.cogboard.logStorage.model import org.bson.Document -data class LogCollectionConfiguration( +data class LogCollectionState( var id: String, var lastLine: Long, var seq: Long @@ -19,17 +19,17 @@ data class LogCollectionConfiguration( const val ID = "_id" const val LAST_LINE = "lastLine" const val SEQ = "seq" + } +} - fun from(document: Document): LogCollectionConfiguration? { - return try { - val id = document.getString(ID) - val lastLine = document.getLong(LAST_LINE) - val seq = document.getLong(SEQ) +fun Document.asLogCollectionState(): LogCollectionState? { + return try { + val id = getString(LogCollectionState.ID) + val lastLine = getLong(LogCollectionState.LAST_LINE) + val seq = getLong(LogCollectionState.SEQ) - LogCollectionConfiguration(id, lastLine, seq) - } catch (_: NullPointerException) { - null - } - } + LogCollectionState(id, lastLine, seq) + } catch (_: NullPointerException) { + null } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt index f285fd815..0f9f0c2ce 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt @@ -18,12 +18,12 @@ data class LogVariableData( companion object { const val HEADER = "header" const val DESCRIPTION = "description" + } +} - fun from(document: Document): LogVariableData? { - val header = document.getString(HEADER) ?: return null - val description = document.getString(DESCRIPTION) ?: return null +fun Document.asLogVariableData(): LogVariableData? { + val header = getString(LogVariableData.HEADER) ?: return null + val description = getString(LogVariableData.DESCRIPTION) ?: return null - return LogVariableData(header, description) - } - } + return LogVariableData(header, description) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 68b803d88..9787aa7ce 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -24,7 +24,7 @@ class LogViewerWidget( ) : BaseWidget(vertx, config, serv) { private val contentRepository: ContentRepository = ContentRepository.DEFAULT private val address = config.endpointProp(Props.URL) - private var consumer: MessageConsumer? = null + private lateinit var consumer: MessageConsumer private val connectionStrategy: ConnectionStrategy? = determineConnectionStrategy() private val logStorage: LogStorage? = connectionStrategy?.let { LogStorage( @@ -84,7 +84,7 @@ class LogViewerWidget( send(logs) } - /** Gets the quarantine rules from the */ + /** Gets the quarantine rules from the content repository. */ private val rules: List get() = contentRepository .get(id) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt index fb126323c..b8235b181 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt @@ -12,8 +12,8 @@ class MockLogParserStrategy : LogParserStrategy { private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""" .toRegex() - override fun parseLine(line: String): Log? { - val groups = regex.matchEntire(line.trim())?.groups ?: return null + override fun parseLine(line: String): Log { + val groups = regex.matchEntire(line.trim())?.groups ?: return makeParsingErrorLog(line) try { val date = LocalDateTime @@ -30,7 +30,7 @@ class MockLogParserStrategy : LogParserStrategy { return Log(date = date, type = type, variableData = variableData) } catch (_: NullPointerException) { - return null + return makeParsingErrorLog(line) } } @@ -40,5 +40,14 @@ class MockLogParserStrategy : LogParserStrategy { private const val TYPE = "type" private const val PROVIDER = "provider" private const val MESSAGE = "message" + + private fun makeParsingErrorLog(line: String): Log = Log( + date = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC), + type = "ERROR", + variableData = listOf( + LogVariableData("MockLogParserStrategy", "No description"), + LogVariableData("Cannot parse a log", "Line causing the error: $line") + ) + ) } } diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt index 2acc66b25..2fd8039c3 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt @@ -11,7 +11,7 @@ class MockLogParserStrategyTest { fun parseSampleLog() { assert(parser.variableFields == listOf("Provider", "Message")) - val output = parser.parseLine(sampleLog) ?: throw AssertionError("Parsed log should not be null") + val output = parser.parseLine(sampleLog) assert(output.type == "DEBUG") assert(output.date == 1636238425L) From e7c9550e69084882f9bf850b8f4cbb49d58815bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 29 Dec 2021 22:40:59 +0100 Subject: [PATCH 138/226] Add an empty quarantine rule --- .../cognifide/cogboard/logStorage/model/QuarantineRule.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt index 92773d9cc..70fbfda8e 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt @@ -14,8 +14,9 @@ data class QuarantineRule( private const val REASON = "reasonField" private const val REGEX = "regExp" private const val ENABLED = "checked" + private val default = QuarantineRule("Default", "", "(?!x)x".toRegex(), false) - fun from(json: JsonObject): QuarantineRule? { + fun from(json: JsonObject): QuarantineRule { return try { QuarantineRule( json.getString(LABEL)!!, @@ -24,7 +25,7 @@ data class QuarantineRule( json.getBoolean(ENABLED)!! ) } catch (_: NullPointerException) { - null + default } } @@ -32,6 +33,6 @@ data class QuarantineRule( array .mapNotNull { it } .mapNotNull { it as? JsonObject } - .mapNotNull { from(it) } + .map { from(it) } } } From d64be4be235436b8d5149e7b858908d1f353a995 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 30 Dec 2021 00:38:24 +0100 Subject: [PATCH 139/226] Update tests (Log parser type) --- functional/cypress-tests/cypress/fixtures/Widgets.js | 3 ++- functional/cypress-tests/cypress/support/widgetDynamicTab.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/functional/cypress-tests/cypress/fixtures/Widgets.js b/functional/cypress-tests/cypress/fixtures/Widgets.js index b65fe77d2..73ed399db 100644 --- a/functional/cypress-tests/cypress/fixtures/Widgets.js +++ b/functional/cypress-tests/cypress/fixtures/Widgets.js @@ -142,7 +142,8 @@ module.exports = { name: 'Log Viewer', endpoint: 'endpoint6', schedulePeriod: '60', - path: '/home/mock/example.txt' + path: '/home/mock/example.txt', + parserType: 'mock' }, serviceCheck: { name: 'Service Check', diff --git a/functional/cypress-tests/cypress/support/widgetDynamicTab.js b/functional/cypress-tests/cypress/support/widgetDynamicTab.js index 4e792b5c7..a45bc0291 100644 --- a/functional/cypress-tests/cypress/support/widgetDynamicTab.js +++ b/functional/cypress-tests/cypress/support/widgetDynamicTab.js @@ -91,6 +91,8 @@ export function fillLogsViewer() { cy.get(`[data-value="${Widgets.logsViewer.endpoint}"]`).click(); cy.fillSchedulePeriod(Widgets.logsViewer.schedulePeriod); cy.get('[data-cy="widget-form-path-input"]').type(Widgets.logsViewer.path); + cy.get('[data-cy="widget-form-log-parser-field-input"]').click(); + cy.get(`[data-value="${Widgets.logsViewer.parserType}"]`).click(); } export function fillServiceCheck() { From 0a9a56a5570984d7fdbd18e16e0140f3533f5c3b Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 30 Dec 2021 00:50:28 +0100 Subject: [PATCH 140/226] Add show only error logs test #441 --- .../types/LogViewerWidget/LogList/LogEntry.js | 6 ++++-- .../LogViewerWidget/Toolbar/FilterPicker/index.js | 7 ++++++- .../cypress-tests/cypress/integration/logsViewer.js | 13 +++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index e9bfb0fee..b8107c3e1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -50,8 +50,10 @@ const LogEntry = ({ > {highlight && } - {type?.toUpperCase()} - {date} + + {type?.toUpperCase()} + + {date} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 45cfa3e5b..d327b750f 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -85,9 +85,14 @@ const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { labelId="log-level-label" value={logLevel} onChange={e => handleLevelSelection(e.target.value)} + data-cy="log-level-menu" > {Object.keys(logLevels).map((key, index) => ( - + {key.toUpperCase()} ))} diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 7ca4a9351..7806ccc60 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -111,4 +111,17 @@ describe('Logs Viewer', () => { closeAdvancedMenu(); }); }); + + describe('Log level', () => { + it('show only error logs', () => { + widget.click('[data-cy="log-level-menu"]'); + widget.click('[data-cy="log-level-menu-option-error"]'); + cy.get('[data-cy="log-entry"] ').each(log => + cy + .wrap(log) + .contains('[data-cy="log-entry-level"]', new RegExp('ERROR')) + .should('exist') + ); + }); + }); }); From acb722d8057a5e17db13f6d6aee180c09e6ede5c Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 30 Dec 2021 16:02:43 +0100 Subject: [PATCH 141/226] Add log level tests #441 --- .../cypress/fixtures/logsViewer.js | 7 +++++ .../cypress/integration/logsViewer.js | 26 ++++++++-------- .../cypress/support/logsViewer/filters.js | 31 +++++++++++++------ 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/functional/cypress-tests/cypress/fixtures/logsViewer.js b/functional/cypress-tests/cypress/fixtures/logsViewer.js index 8185eb849..c244995e3 100644 --- a/functional/cypress-tests/cypress/fixtures/logsViewer.js +++ b/functional/cypress-tests/cypress/fixtures/logsViewer.js @@ -8,3 +8,10 @@ export const filters = { regExp: 'amet' } }; + +export const logLevels = [ + { level: 10, value: "debug" }, + { level: 20, value: "info"}, + { level: 30, value: "warning" }, + { level: 40, value: "error" } +] diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 7806ccc60..b62d2693c 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -8,9 +8,10 @@ import { fillFormField, logsMatchFilter, submitForm, - assertChip + assertChip, + logsMatchLogLevel, } from '../support/logsViewer/filters'; -import { filters } from '../fixtures/logsViewer'; +import { filters, logLevels } from '../fixtures/logsViewer'; const dashboardName = 'Welcome to Cogboard'; const ametFilter = filters.amet; @@ -27,7 +28,7 @@ describe('Logs Viewer', () => { cy.clickAddWidgetButton(); widget = createWidget(logsViewer.name).configure(false, { cols: 8, - rows: 2 + rows: 2, }); }); @@ -96,7 +97,7 @@ describe('Logs Viewer', () => { it('should delete filters', () => { openAdvancedMenu(); - cy.get('[data-cy="delete-filter-delete-button"]').each(filter => { + cy.get('[data-cy="delete-filter-delete-button"]').each((filter) => { cy.wrap(filter).click(); cy.get('[data-cy="confirmation-dialog-ok"]').click(); }); @@ -113,15 +114,14 @@ describe('Logs Viewer', () => { }); describe('Log level', () => { - it('show only error logs', () => { - widget.click('[data-cy="log-level-menu"]'); - widget.click('[data-cy="log-level-menu-option-error"]'); - cy.get('[data-cy="log-entry"] ').each(log => - cy - .wrap(log) - .contains('[data-cy="log-entry-level"]', new RegExp('ERROR')) - .should('exist') - ); + logLevels.forEach((selectedLevel) => { + it(`show logs with greater or equal level to ${selectedLevel.value}`, () => { + widget.click('[data-cy="log-level-menu"]'); + widget.click( + `[data-cy="log-level-menu-option-${selectedLevel.value}"]` + ); + logsMatchLogLevel(selectedLevel, logLevels); + }); }); }); }); diff --git a/functional/cypress-tests/cypress/support/logsViewer/filters.js b/functional/cypress-tests/cypress/support/logsViewer/filters.js index 461db427b..6a2a4df88 100644 --- a/functional/cypress-tests/cypress/support/logsViewer/filters.js +++ b/functional/cypress-tests/cypress/support/logsViewer/filters.js @@ -1,10 +1,23 @@ -export const logsMatchFilter = regExp => - cy.get('[data-cy="log-entry"] ').each(log => - cy - .wrap(log) - .contains('[data-cy="log-variable-data"] p', new RegExp(regExp)) - .should('exist') +const logsContains = (logPartSelector, regExp) => + cy + .get('[data-cy="log-entry"] ') + .each((log) => + cy.wrap(log).contains(logPartSelector, regExp).should('exist') + ); + +export const logsMatchFilter = (regExp) => + logsContains('[data-cy="log-variable-data"] p', new RegExp(regExp)); + +export const logsMatchLogLevel = (selectedLevel, levels) => { + const greaterLogLevels = levels.filter( + (level) => level.level >= selectedLevel.level ); + const regExp = new RegExp( + greaterLogLevels.map((lvl) => lvl.value).join('|'), + 'i' + ); + logsContains('[data-cy="log-entry-level"]', regExp); +}; export const openAdvancedMenu = () => cy.get('[data-cy="advanced-filters-button"]').click(); @@ -25,15 +38,13 @@ export const assertChip = (widget, label, chainer = 'exist') => { }; export const fillFormField = (field, value) => { - cy.get(`[data-cy="filter-form-${field}-input"]`) - .clear() - .type(value); + cy.get(`[data-cy="filter-form-${field}-input"]`).clear().type(value); }; export const submitForm = () => cy.get('[data-cy="filter-form-submit-button"]').click(); -export const addFilter = filter => { +export const addFilter = (filter) => { cy.get('[data-cy="add-filter-add-button"]').click(); fillFormField('label', filter.label); fillFormField('reg-exp', filter.regExp); From e4eff4a7349fc3e2e7a1d9922c9554ea7f615bae Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 30 Dec 2021 16:52:41 +0100 Subject: [PATCH 142/226] Add selectLogLevel func #441 --- functional/cypress-tests/cypress/integration/logsViewer.js | 7 +++---- .../cypress-tests/cypress/support/logsViewer/filters.js | 5 +++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index b62d2693c..9a6f48050 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -10,6 +10,7 @@ import { submitForm, assertChip, logsMatchLogLevel, + selectLogLevel, } from '../support/logsViewer/filters'; import { filters, logLevels } from '../fixtures/logsViewer'; @@ -116,11 +117,9 @@ describe('Logs Viewer', () => { describe('Log level', () => { logLevels.forEach((selectedLevel) => { it(`show logs with greater or equal level to ${selectedLevel.value}`, () => { - widget.click('[data-cy="log-level-menu"]'); - widget.click( - `[data-cy="log-level-menu-option-${selectedLevel.value}"]` - ); + selectLogLevel(selectedLevel.value); logsMatchLogLevel(selectedLevel, logLevels); + selectLogLevel('info'); // default }); }); }); diff --git a/functional/cypress-tests/cypress/support/logsViewer/filters.js b/functional/cypress-tests/cypress/support/logsViewer/filters.js index 6a2a4df88..11bbed933 100644 --- a/functional/cypress-tests/cypress/support/logsViewer/filters.js +++ b/functional/cypress-tests/cypress/support/logsViewer/filters.js @@ -19,6 +19,11 @@ export const logsMatchLogLevel = (selectedLevel, levels) => { logsContains('[data-cy="log-entry-level"]', regExp); }; +export const selectLogLevel = (levelSlug) => { + cy.get('[data-cy="log-level-menu"]').click(); + cy.get(`[data-cy="log-level-menu-option-${levelSlug}"]`).click(); +}; + export const openAdvancedMenu = () => cy.get('[data-cy="advanced-filters-button"]').click(); From de96320a8b1d47366a3a272929f0bf378146d26d Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 30 Dec 2021 19:05:18 +0100 Subject: [PATCH 143/226] Add date span tests #441 --- .../Toolbar/DateRangePicker/CustomPicker.js | 9 +++-- .../types/LogViewerWidget/Toolbar/index.js | 1 + .../cypress/integration/logsViewer.js | 33 ++++++++++++++++--- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js index d55d40f8b..6e70cf026 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js @@ -7,20 +7,25 @@ import { CustomDateTimePicker } from './styled'; -const CustomPicker = ({ value, onChange, ...props }) => { +const CustomPicker = ({ value, onChange, label, ...props }) => { const handleChange = data => onChange(data?.seconds(0).milliseconds(0)); return ( {value && ( - onChange(null)}> + onChange(null)} + data-cy={`date-time-picker-${label?.toLowerCase()}-clear`} + > )} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index f2f4d2a1d..ff07155b1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -62,6 +62,7 @@ const Toolbar = ({ size="small" theme={theme} onClick={handleClearLogs} + data-cy="clear-logs-button" > Clear logs diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 9a6f48050..893214216 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -10,7 +10,7 @@ import { submitForm, assertChip, logsMatchLogLevel, - selectLogLevel, + selectLogLevel } from '../support/logsViewer/filters'; import { filters, logLevels } from '../fixtures/logsViewer'; @@ -29,7 +29,7 @@ describe('Logs Viewer', () => { cy.clickAddWidgetButton(); widget = createWidget(logsViewer.name).configure(false, { cols: 8, - rows: 2, + rows: 2 }); }); @@ -98,7 +98,7 @@ describe('Logs Viewer', () => { it('should delete filters', () => { openAdvancedMenu(); - cy.get('[data-cy="delete-filter-delete-button"]').each((filter) => { + cy.get('[data-cy="delete-filter-delete-button"]').each(filter => { cy.wrap(filter).click(); cy.get('[data-cy="confirmation-dialog-ok"]').click(); }); @@ -115,7 +115,7 @@ describe('Logs Viewer', () => { }); describe('Log level', () => { - logLevels.forEach((selectedLevel) => { + logLevels.forEach(selectedLevel => { it(`show logs with greater or equal level to ${selectedLevel.value}`, () => { selectLogLevel(selectedLevel.value); logsMatchLogLevel(selectedLevel, logLevels); @@ -123,4 +123,29 @@ describe('Logs Viewer', () => { }); }); }); + + describe('Date span', () => { + it('sets begin date on CLEAR LOGS button click', () => { + widget.click('[data-cy="clear-logs-button"'); + + // begin date span picker should not be empty + cy.get('[data-cy="date-time-picker-begin"] .MuiInput-root input').should( + 'not.have.value', + '' + ); + }); + + it('filters logs by begin date span', () => + cy.get('[data-cy="log-entry"]').should('not.exist')); + + it('removes date when X icon is clicked', () => { + widget.click('[data-cy="date-time-picker-begin-clear"]'); + // should be empty + cy.get('[data-cy="date-time-picker-begin"] .MuiInput-root input').should( + 'have.value', + '' + ); + cy.get('[data-cy="log-entry"]').should('exist'); + }); + }); }); From 9e818eb7ab1ea463d0f1208a530396bcbc38fafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Thu, 30 Dec 2021 21:09:47 +0100 Subject: [PATCH 144/226] Add similar logs buttons --- cogboard-webapp/src/components/AddItem.js | 17 +++++++- .../types/LogViewerWidget/LogList/LogEntry.js | 43 ++++++++++++++++--- .../types/LogViewerWidget/LogList/index.js | 6 ++- .../types/LogViewerWidget/LogList/styled.js | 25 +++++++++-- .../AdvancedFiltersMenu/FilterForm.js | 32 ++++++++++---- .../FilterPicker/AdvancedFiltersMenu/index.js | 39 ++++++++++++++--- .../Toolbar/FilterPicker/index.js | 10 ++++- .../Toolbar/QuarantineModal/QuarantineForm.js | 34 +++++++++++---- .../Toolbar/QuarantineModal/index.js | 25 +++++++++-- .../types/LogViewerWidget/Toolbar/index.js | 6 ++- .../widgets/types/LogViewerWidget/index.js | 10 +++++ cogboard-webapp/src/hooks/index.js | 3 +- 12 files changed, 210 insertions(+), 40 deletions(-) diff --git a/cogboard-webapp/src/components/AddItem.js b/cogboard-webapp/src/components/AddItem.js index 24c38056e..6d338e0bb 100644 --- a/cogboard-webapp/src/components/AddItem.js +++ b/cogboard-webapp/src/components/AddItem.js @@ -1,12 +1,25 @@ -import React, { cloneElement } from 'react'; +import React, { cloneElement, useEffect } from 'react'; import { useToggle } from '../hooks'; import AddButton from './AddButton'; import AppDialog from './AppDialog'; -const AddItem = ({ itemName, largeButton, submitAction, children }) => { +const AddItem = ({ + itemName, + largeButton, + submitAction, + children, + shouldOpen +}) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); + useEffect(() => { + if (shouldOpen) { + openDialog(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shouldOpen]); + const handleAddItemClick = event => { event.stopPropagation(); openDialog(); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index e9bfb0fee..486458c3e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -9,8 +9,13 @@ import { CustomAccordion, VariableGridSchema, HighlightedText, - HighlightMark + HighlightMark, + VerticalStack, + SimilarLogsButtonsContainer, + SimilarLogsButton } from './styled'; +import { useSelector } from 'react-redux'; +import { getIsAuthenticated } from '../../../../../selectors'; const LogEntry = ({ type, @@ -18,9 +23,15 @@ const LogEntry = ({ variableData, template, search, - highlight + highlight, + setFilterSimilarLogs, + setQuarantineSimilarLogs }) => { const [expanded, setExpanded] = useState(false); + const isAuthenticated = useSelector(getIsAuthenticated); + + const getLastVariableHeader = () => + variableData[variableData.length - 1]?.header ?? ''; const VariablePart = ({ description }) => { const variableFieldsTemplate = getGridTemplate(template); @@ -56,9 +67,31 @@ const LogEntry = ({ - - - + + + + + + setFilterSimilarLogs(getLastVariableHeader())} + > + Filter similar logs + + {isAuthenticated && ( + + setQuarantineSimilarLogs(getLastVariableHeader()) + } + > + Add similar logs to quarantine + + )} + + ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 7e8022ed2..f9b382fa8 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -26,7 +26,9 @@ export default function LogList({ template, search, shouldFollowLogs, - handleFollowChange + handleFollowChange, + setFilterSimilarLogs, + setQuarantineSimilarLogs }) { const theme = useTheme(); const scrollerRef = useRef(null); @@ -81,6 +83,8 @@ export default function LogList({ template={template} search={search} highlight={isLogHighlighted(log, search)} + setFilterSimilarLogs={setFilterSimilarLogs} + setQuarantineSimilarLogs={setQuarantineSimilarLogs} /> ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index e92742e43..d23901f74 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -1,7 +1,7 @@ import styled from '@emotion/styled/macro'; import { Virtuoso } from 'react-virtuoso'; import { COLORS } from '../../../../../constants'; -import { Typography, Accordion } from '@material-ui/core'; +import { Typography, Accordion, Button } from '@material-ui/core'; import logLevels from '../logLevels'; export const Container = styled.div` @@ -18,6 +18,12 @@ export const Header = styled.div` padding: 0.25em 0; `; +export const VerticalStack = styled.div` + width: 100%; + display: flex; + flex-direction: column; +`; + export const GridSchema = styled.div` width: 100%; display: grid; @@ -88,13 +94,14 @@ export const CustomAccordion = styled(Accordion)` min-height: unset; } - .MuiButtonBase-root .MuiIconButton-root { + .MuiAccordionSummary-root .MuiIconButton-root { position: absolute; top: 0; right: 1em; } - .MuiButtonBase-root { + .MuiAccordionSummary-root, + .MuiAccordionSummary-root .MuiButtonBase-root { padding: unset; } @@ -121,3 +128,15 @@ export const HighlightMark = styled.div` transform: rotate(45deg); background-color: ${COLORS.YELLOW}; `; + +export const SimilarLogsButtonsContainer = styled.div` + align-self: flex-end; + display: flex; + flex-direction: row; + gap: 0.5rem; + margin: 0.25rem 0.5em 0.25em 0; +`; + +export const SimilarLogsButton = styled(Button)` + display: block; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js index c47d0f086..2f88cdd22 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js @@ -1,18 +1,30 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { createValidationSchema } from '../../../../../../validation'; import { useFormData } from '../../../../../../../hooks'; import { Button } from '@material-ui/core'; import DynamicForm from '../../../../../../DynamicForm'; import { StyledCancelButton } from './styled'; +import dialogFields from '../../../../../dialogFields'; const FilterForm = ({ filters, onSubmit, handleCancel, id, + filterSimilarLogsState, ...initialFormValues }) => { + const [filterSimilarLogs, setFilterSimilarLogs] = filterSimilarLogsState; + + useEffect(() => { + if (filterSimilarLogs) { + setFieldValue(dialogFields.RegExpField.name, filterSimilarLogs); + setFilterSimilarLogs(null); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filterSimilarLogs]); + const formFields = ['LabelField', 'RegExpField']; const constraints = { LabelField: { @@ -21,15 +33,17 @@ const FilterForm = ({ labelId: id } }; - const validationSchema = createValidationSchema(formFields, constraints); - const { values, handleChange, withValidation, errors } = useFormData( - initialFormValues, - { - initialSchema: validationSchema, - onChange: true - } - ); + const { + values, + handleChange, + withValidation, + errors, + setFieldValue + } = useFormData(initialFormValues, { + initialSchema: validationSchema, + onChange: true + }); return (
diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 3d3a7c2a3..0c01069ef 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useToggle } from '../../../../../../../hooks'; import { v4 } from 'uuid'; import { getFilters, saveFilters } from '../helpers'; @@ -20,8 +20,25 @@ import DeleteItem from '../../../../../../DeleteItem'; import FilterForm from './FilterForm'; import { StyledExitButton } from './styled'; -const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { +const AdvancedFiltersMenu = ({ + widgetLocalStorage, + wid, + quarantine, + filterSimilarLogsState, + quarantineSimilarLogsState +}) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); + + const [filterSimilarLogs] = filterSimilarLogsState; + const [quarantineSimilarLogs] = quarantineSimilarLogsState; + + useEffect(() => { + if (filterSimilarLogs || quarantineSimilarLogs) { + openDialog(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filterSimilarLogs, quarantineSimilarLogs]); + const filters = getFilters(widgetLocalStorage); const addFilter = values => { @@ -120,10 +137,22 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { deleteFilter )} - - + + - + { +const FilterPicker = ({ + widgetLocalStorage, + wid, + quarantine, + filterSimilarLogsState, + quarantineSimilarLogsState +}) => { const regExpFilters = getFilters(widgetLocalStorage); const logLevel = getLevel(widgetLocalStorage); @@ -97,6 +103,8 @@ const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { widgetLocalStorage={widgetLocalStorage} wid={wid} quarantine={quarantine} + filterSimilarLogsState={filterSimilarLogsState} + quarantineSimilarLogsState={quarantineSimilarLogsState} /> ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js index f8f7cbd1e..29375c4ea 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js @@ -1,18 +1,33 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { createValidationSchema } from '../../../../../validation'; import { useFormData } from '../../../../../../hooks'; import DynamicForm from '../../../../../DynamicForm'; import { Button } from '@material-ui/core'; import InfoIcon from '@material-ui/icons/Info'; import { StyledHorizontalContainer, StyledCancelButton } from './styled'; +import dialogFields from '../../../../dialogFields'; const QuarantineForm = ({ filters, onSubmit, handleCancel, id, + quarantineSimilarLogsState, ...initialFormValues }) => { + const [ + quarantineSimilarLogs, + setQuarantineSimilarLogs + ] = quarantineSimilarLogsState; + + useEffect(() => { + if (quarantineSimilarLogs) { + setFieldValue(dialogFields.RegExpField.name, quarantineSimilarLogs); + setQuarantineSimilarLogs(null); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [quarantineSimilarLogs]); + const formFields = ['LabelField', 'RegExpField', 'ReasonField']; const constraints = { LabelField: { @@ -23,13 +38,16 @@ const QuarantineForm = ({ }; const validationSchema = createValidationSchema(formFields, constraints); - const { values, handleChange, withValidation, errors } = useFormData( - initialFormValues, - { - initialSchema: validationSchema, - onChange: true - } - ); + const { + values, + handleChange, + withValidation, + errors, + setFieldValue + } = useFormData(initialFormValues, { + initialSchema: validationSchema, + onChange: true + }); return ( diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index ad0a5aec0..b8342c140 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import { v4 } from 'uuid'; import { @@ -18,10 +18,19 @@ import QuarantineForm from './QuarantineForm'; import EditQFilter from './EditQFilter'; import DeleteItem from '../../../../../DeleteItem'; -const QuarantineModal = ({ wid, quarantine }) => { +const QuarantineModal = ({ wid, quarantine, quarantineSimilarLogsState }) => { const isAuthenticated = useSelector(getIsAuthenticated); const [dialogOpened, openDialog, handleDialogClose] = useToggle(); + const [quarantineSimilarLogs] = quarantineSimilarLogsState; + + useEffect(() => { + if (quarantineSimilarLogs) { + openDialog(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [quarantineSimilarLogs]); + const handleQuarantineClick = event => { event.stopPropagation(); openDialog(); @@ -119,8 +128,16 @@ const QuarantineModal = ({ wid, quarantine }) => { deleteAction )} - - + + { const theme = useTheme(); @@ -44,6 +46,8 @@ const Toolbar = ({ widgetLocalStorage={widgetLocalStorage} wid={wid} quarantine={quarantine} + filterSimilarLogsState={filterSimilarLogsState} + quarantineSimilarLogsState={quarantineSimilarLogsState} /> diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index e7680d630..aec82886c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -43,6 +43,9 @@ const LogViewerWidget = ({ id }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [widgetData]); + const [filterSimilarLogs, setFilterSimilarLogs] = useState(null); + const [quarantineSimilarLogs, setQuarantineSimilarLogs] = useState(null); + return ( { storedLogs.length > 0 && storedLogs[storedLogs.length - 1] } + filterSimilarLogsState={[filterSimilarLogs, setFilterSimilarLogs]} + quarantineSimilarLogsState={[ + quarantineSimilarLogs, + setQuarantineSimilarLogs + ]} /> {storedLogs && ( { search={searchFilter} shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} + setFilterSimilarLogs={setFilterSimilarLogs} + setQuarantineSimilarLogs={setQuarantineSimilarLogs} /> )} diff --git a/cogboard-webapp/src/hooks/index.js b/cogboard-webapp/src/hooks/index.js index d5f0b6d69..7258c63f5 100644 --- a/cogboard-webapp/src/hooks/index.js +++ b/cogboard-webapp/src/hooks/index.js @@ -93,7 +93,8 @@ export const useFormData = (data, config = {}) => { withValidation, errors, validationSchema, - setValidationSchema + setValidationSchema, + setFieldValue }; }; From fafe7d3fd7fa349fb26c3628d3971e85a4c2c7af Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 31 Dec 2021 11:31:56 +0100 Subject: [PATCH 145/226] Add ToggleIconButton, update toolbar #447 --- .../FilterPicker/AdvancedFiltersMenu/index.js | 14 +-- .../Toolbar/FilterPicker/index.js | 98 ++++++++++--------- .../Toolbar/FilterPicker/styled.js | 6 ++ .../Toolbar/ToggleIconButton.js | 27 +++++ .../types/LogViewerWidget/Toolbar/index.js | 33 +++---- .../types/LogViewerWidget/Toolbar/styled.js | 21 ++-- 6 files changed, 118 insertions(+), 81 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 3d3a7c2a3..4f7a03e9f 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -3,9 +3,9 @@ import { useToggle } from '../../../../../../../hooks'; import { v4 } from 'uuid'; import { getFilters, saveFilters } from '../helpers'; import QuarantineModal from '../../QuarantineModal'; +import ToggleIconButton from '../../ToggleIconButton'; import { - Button, List, ListItem, ListItemText, @@ -13,6 +13,7 @@ import { Switch, Tooltip } from '@material-ui/core'; +import AddIcon from '@material-ui/icons/Add'; import AppDialog from '../../../../../../AppDialog'; import AddItem from '../../../../../../AddItem'; import EditFilter from './EditFilter'; @@ -97,14 +98,13 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { return ( <> - + /> { return ( - - - {regExpFilters.length > 0 ? `Filters` : `No filters defined`} - - filter.checked)} - onChange={e => handleSelection(e.target.value)} - renderValue={selected => ( - - {selected.map(({ id, label, regExp }) => ( - - handleDelete(id)} - onMouseDown={e => e.stopPropagation()} - data-cy="filters-chip" - /> - - ))} - - )} - > - {regExpFilters.map(filter => ( - - {filter.label} - - ))} - - - Log level { ))} - + + + + {regExpFilters.length > 0 ? `Filters` : `No filters defined`} + + filter.checked)} + onChange={e => handleSelection(e.target.value)} + renderValue={selected => ( + + {selected.map(({ id, label, regExp }) => ( + + handleDelete(id)} + onMouseDown={e => e.stopPropagation()} + data-cy="filters-chip" + /> + + ))} + + )} + > + {regExpFilters.map(filter => ( + + {filter.label} + + ))} + + + + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js index c1cea6e28..e1812ca87 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js @@ -30,3 +30,9 @@ export const StyledChip = styled(Chip)` StyledChip.defaultProps = { size: 'small' }; + +export const FiltersWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-end; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js new file mode 100644 index 000000000..f69276550 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { string, elementType, bool } from 'prop-types'; +import { Tooltip } from '@material-ui/core'; +import { StyledIconButton } from './styled'; + +const ToggleIconButton = ({ tooltip, Icon, enabled, ...props }) => { + return ( + + + + + + ); +}; + +ToggleIconButton.propTypes = { + tooltip: string, + Icon: elementType.isRequired, + enabled: bool +}; + +ToggleIconButton.defaultProps = { + tooltip: '', + enabled: false +}; + +export default ToggleIconButton; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index f2f4d2a1d..38f09e0a5 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -2,12 +2,12 @@ import React from 'react'; import moment from 'moment-timezone'; import { saveDateSpan } from './DateRangePicker/helpers'; -import { Button, useTheme } from '@material-ui/core'; -import { RedButton, Wrapper } from './styled'; +import ToggleIconButton from './ToggleIconButton'; +import { Wrapper } from './styled'; import SearchInput from './SearchInput'; import ToolbarGroup from './ToolbarGroup'; import DateRangePicker from './DateRangePicker'; -import GetAppIcon from '@material-ui/icons/GetApp'; +import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; @@ -20,8 +20,6 @@ const Toolbar = ({ handleFollowChange, lastLog }) => { - const theme = useTheme(); - const handleClearLogs = () => { const date = lastLog?.date; if (date) { @@ -49,23 +47,18 @@ const Toolbar = ({ - - + - - Clear logs - + Icon={DeleteIcon} + /> ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index d3ec3eedb..b2a83a6fb 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -1,10 +1,6 @@ import styled from '@emotion/styled/macro'; -import { Button } from '@material-ui/core'; - -export const RedButton = styled(Button)` - color: white; - background-color: ${({ theme }) => theme.palette.status.FAIL}; -`; +import { IconButton } from '@material-ui/core'; +import { COLORS } from '../../../../../constants'; export const Wrapper = styled.div` top: 0; @@ -16,3 +12,16 @@ export const Wrapper = styled.div` justify-content: space-between; gap: 1em; `; + +export const StyledIconButton = styled(IconButton)` + ${props => + props.enabled + ? ` + color: ${COLORS.BLUE}; + background-color: ${COLORS.LIGHT_SHADE} + ` + : ''} +`; +StyledIconButton.defaultProps = { + size: 'small' +}; From a61db69f1d3afa04ba7243d3b71d04a17d86f32a Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 31 Dec 2021 12:00:16 +0100 Subject: [PATCH 146/226] Update search input rwd #447 --- .../types/LogViewerWidget/Toolbar/SearchInput/index.js | 7 +++---- .../LogViewerWidget/Toolbar/SearchInput/styled.js | 10 +++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index 45d7f2bb7..a677c66f2 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -2,8 +2,7 @@ import React, { useState, useEffect } from 'react'; import { func, number } from 'prop-types'; import { useDebounce } from '../../../../../../hooks'; -import { TextField } from '@material-ui/core'; -import { Wrapper, CustomIconButton } from './styled'; +import { Wrapper, CustomIconButton, StyledTextField } from './styled'; import SearchIcon from '@material-ui/icons/Search'; import CloseIcon from '@material-ui/icons/Close'; @@ -26,8 +25,8 @@ const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { return ( - diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js index 94214039e..cfed1cdc2 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js @@ -1,5 +1,5 @@ import styled from '@emotion/styled/macro'; -import { IconButton } from '@material-ui/core'; +import { IconButton, TextField } from '@material-ui/core'; export const Wrapper = styled.div` display: flex; @@ -16,3 +16,11 @@ CustomIconButton.defaultProps = { variant: 'outlined', size: 'small' }; + +export const StyledTextField = styled(TextField)` + min-width: 5rem; + + & input { + margin-right: 1.8rem; + } +`; From b1d1d8620c9a30eefeda4d553f3c61aa4426568e Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 31 Dec 2021 14:24:19 +0100 Subject: [PATCH 147/226] Fix toolbar width #447 --- .../types/LogViewerWidget/Toolbar/styled.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index b2a83a6fb..be64ca6a1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -3,14 +3,23 @@ import { IconButton } from '@material-ui/core'; import { COLORS } from '../../../../../constants'; export const Wrapper = styled.div` + width: 100%; top: 0; position: absolute; - overflow-x: hidden; + overflow-x: scroll; display: flex; flex-direction: row; - align-items: stretch; - justify-content: space-between; + justify-content: flex-start; gap: 1em; + + + /* hide scrollbar */ + &::-webkit-scrollbar { + display: none; + } + -ms-overflow-style: none; + scrollbar-width: none; +} `; export const StyledIconButton = styled(IconButton)` From 04384ae0bd5ef682b9a8b475de433bf86ae6bc52 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 2 Jan 2022 23:06:23 +0100 Subject: [PATCH 148/226] Move scrollbar of toolbar #447 --- .../widgets/types/LogViewerWidget/Toolbar/styled.js | 9 +-------- .../components/widgets/types/LogViewerWidget/styled.js | 1 + 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index be64ca6a1..ea9f866e3 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -11,14 +11,7 @@ export const Wrapper = styled.div` flex-direction: row; justify-content: flex-start; gap: 1em; - - - /* hide scrollbar */ - &::-webkit-scrollbar { - display: none; - } - -ms-overflow-style: none; - scrollbar-width: none; + overflow-x: hidden; } `; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js index c5244f6ac..6a0e955f9 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js @@ -4,4 +4,5 @@ export const Container = styled.div` position: relative; height: 100%; width: 100%; + min-width: 58rem; `; From d241cf2795970ac3c8c29b7bf741c5702fcc5d84 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 2 Jan 2022 23:32:31 +0100 Subject: [PATCH 149/226] Update toolbar width #447 --- .../src/components/widgets/types/LogViewerWidget/styled.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js index 6a0e955f9..a101a09c6 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js @@ -4,5 +4,5 @@ export const Container = styled.div` position: relative; height: 100%; width: 100%; - min-width: 58rem; + min-width: 54rem; `; From 5467e138811bb7a43eabf65946a2827ae3bb21db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 2 Jan 2022 23:45:51 +0100 Subject: [PATCH 150/226] Remove some nullable fields --- .../cogboard/logStorage/LogController.kt | 23 +++-- .../cogboard/logStorage/LogStorage.kt | 85 ++++++++----------- .../cogboard/logStorage/model/Log.kt | 23 ++--- .../logStorage/model/LogCollectionState.kt | 16 ++-- .../logStorage/model/LogVariableData.kt | 10 +-- .../logStorage/model/QuarantineRule.kt | 1 - .../widget/type/logviewer/LogViewerWidget.kt | 2 +- 7 files changed, 63 insertions(+), 97 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt index f0a66b323..134ad99c4 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt @@ -24,13 +24,13 @@ class LogController : RoutingHandlerFactory { } } - fun getLogs(id: String): List { + private fun getLogs(id: String): List { val logLines = boards() .loadConfig() - ?.getJsonObject(Props.WIDGETS) - ?.getJsonObject(Props.WIDGETS_BY_ID) - ?.getJsonObject(id) - ?.getInteger(Props.LOG_LINES) + .getJsonObject(Props.WIDGETS) + .getJsonObject(Props.WIDGETS_BY_ID) + .getJsonObject(id) + .getInteger(Props.LOG_LINES) ?: LogViewerWidget.DEFAULT_LOG_LINES.toInt() return fetchLogs(id, logLines) @@ -38,12 +38,11 @@ class LogController : RoutingHandlerFactory { private fun fetchLogs(id: String, logLines: Int): List { return LogStorage.database - ?.getCollection(id) - ?.find() - ?.sort(Sorts.descending(Log.SEQ)) - ?.limit(logLines) - ?.mapNotNull { it?.asLog() } - ?.sortedBy { it.seq } - ?: emptyList() + .getCollection(id) + .find() + .sort(Sorts.descending(Log.SEQ)) + .limit(logLines) + .map { it.asLog() } + .sortedBy { it.seq } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index 96c3ed653..00da57c6d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -43,34 +43,34 @@ class LogStorage( override fun start() { super.start() - logsCollection?.createIndex(Indexes.descending(Log.SEQ)) + logsCollection.createIndex(Indexes.descending(Log.SEQ)) } /** Returns a logs collection associated with this widget. */ - private val logsCollection: MongoCollection? - get() = database?.getCollection(config.id) + private val logsCollection: MongoCollection + get() = database.getCollection(config.id) // Storage configuration /** Returns a logs collection configuration associated with this widget (if present). */ private val collectionState: LogCollectionState? get() = stateCollection - ?.find(eq(Log.ID, config.id)) - ?.first() - ?.let { it.asLogCollectionState() } + .find(eq(Log.ID, config.id)) + .first() + ?.asLogCollectionState() /** Saves or deletes a logs collection [state] associated with this widget. */ private fun saveState(state: LogCollectionState?) { if (state != null) { val options = ReplaceOptions().upsert(true) stateCollection - ?.replaceOne( + .replaceOne( eq(LogCollectionState.ID, config.id), state.toDocument(), options ) } else { - stateCollection?.deleteOne((eq(LogCollectionState.ID, config.id))) + stateCollection.deleteOne((eq(LogCollectionState.ID, config.id))) } } @@ -78,20 +78,20 @@ class LogStorage( /** Deletes all logs from the collection. */ private fun deleteAllLogs() { - logsCollection?.drop() + logsCollection.drop() } /** Deletes the [n] first logs (ordered by their sequence number). */ private fun deleteFirstLogs(n: Long) { - val collection = logsCollection ?: return - val ids = collection + val ids = logsCollection .find() .sort(ascending(Log.SEQ)) .limit(n.toInt()) - .mapNotNull { it?.getObjectId(Log.ID) } + .map { it.getObjectId(Log.ID) } + .toList() if (ids.isNotEmpty()) { try { - val result = collection.deleteMany(`in`(Log.ID, ids)) + val result = logsCollection.deleteMany(`in`(Log.ID, ids)) LOGGER.debug("Deleted ${result.deletedCount} first logs") } catch (exception: MongoException) { LOGGER.error("Cannot delete first logs: $exception") @@ -101,11 +101,10 @@ class LogStorage( /** Deletes old logs (based on the number of days before expiration). */ private fun deleteOldLogs() { - val collection = logsCollection ?: return val now = Instant.now().epochSecond val beforeTimestamp = now - (config.expirationDays * DAY_TO_TIMESTAMP) try { - val result = collection.deleteMany(lt(Log.INSERTED_ON, beforeTimestamp)) + val result = logsCollection.deleteMany(lt(Log.INSERTED_ON, beforeTimestamp)) LOGGER.debug("Deleted ${result.deletedCount} old logs") } catch (exception: MongoException) { LOGGER.error("Cannot delete old logs: $exception") @@ -114,16 +113,13 @@ class LogStorage( /** Deletes oldest logs when logs take up too much space. */ private fun deleteSpaceConsumingLogs() { - val database = database ?: return - val collection = logsCollection ?: return - val size = database .runCommand(Document(STATS_COMMAND, config.id)) - .getInteger(STATS_SIZE) ?: 0 + .getInteger(STATS_SIZE) val maxSize = config.fileSizeMB * MB_TO_BYTES if (size > 0 && size > maxSize) { val deleteFactor = ((size - maxSize).toDouble() / size) - val logCount = collection.countDocuments() + val logCount = logsCollection.countDocuments() val toDelete = (logCount.toDouble() * deleteFactor).toLong() if (toDelete > 0) { LOGGER.debug("Deleting $toDelete logs as the size $size exceeds maximum size of $maxSize") @@ -145,14 +141,12 @@ class LogStorage( /** Deletes logs not matching to the rules from the database. */ fun filterExistingLogs() { - val collection = logsCollection ?: return - val fieldName = Log.VARIABLE_DATA + "." + LogVariableData.HEADER val regexes = enabledRegexes.map { regex(fieldName, it.pattern) } if (regexes.isEmpty()) { return } - collection.deleteMany(or(regexes)) + logsCollection.deleteMany(or(regexes)) } /** Downloads new logs and filters them by quarantine rules. */ @@ -178,7 +172,7 @@ class LogStorage( sequence++ } - logsCollection?.insertMany(logs.map { it.toDocument() }) + logsCollection.insertMany(logs.map { it.toDocument() }) } /** Checks how many logs to download, downloads them and saves them to the database. */ @@ -227,7 +221,7 @@ class LogStorage( } val response = prepareResponse(insertedLogs) - vertx?.eventBus()?.send(config.eventBusAddress, response) + vertx.eventBus().send(config.eventBusAddress, response) } /** Deletes all data associated with the widget. */ @@ -248,39 +242,28 @@ class LogStorage( private val MONGO_PASSWORD = System.getenv("MONGO_PASSWORD") ?: "root" private val MONGO_HOST = System.getenv("MONGO_HOST") ?: "mongo" private val MONGO_PORT = System.getenv("MONGO_PORT")?.toIntOrNull() ?: 27017 - private var mongoClient: MongoClient? = null /** Returns a shared instance of the Mongo client. */ - private val client: MongoClient? - get() { - if (mongoClient != null) { - return mongoClient - } - try { - val uri = URI( - MONGO_SCHEME, - "$MONGO_USERNAME:$MONGO_PASSWORD", - MONGO_HOST, - MONGO_PORT, - null, - null, - null - ) - mongoClient = MongoClients.create(uri.toString()) - return mongoClient - } catch (exception: MongoException) { - LOGGER.error("Cannot create a mongo client: $exception") - } - return null + private val mongoClient: MongoClient by lazy { + val uri = URI( + MONGO_SCHEME, + "$MONGO_USERNAME:$MONGO_PASSWORD", + MONGO_HOST, + MONGO_PORT, + null, + null, + null + ) + MongoClients.create(uri.toString()) } /** Returns a database for storing logs and collection states. */ - val database: MongoDatabase? - get() = client?.getDatabase(DATABASE_NAME) + val database: MongoDatabase + get() = mongoClient.getDatabase(DATABASE_NAME) /** Returns a state collection. */ - val stateCollection: MongoCollection? - get() = database?.getCollection(STATE_COLLECTION_NAME) + val stateCollection: MongoCollection + get() = database.getCollection(STATE_COLLECTION_NAME) private val LOGGER: Logger = LoggerFactory.getLogger(LogStorage::class.java) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt index f6c30b4d8..60433a7cf 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt @@ -45,18 +45,11 @@ data class Log( } } -fun Document.asLog(): Log? { - return try { - val id = getObjectId(Log.ID)!! - val seq = getLong(Log.SEQ)!! - val insertedOn = getLong(Log.INSERTED_ON)!! - val date = getLong(Log.DATE)!! - val type = getString(Log.TYPE)!! - - val variableData = getList(Log.VARIABLE_DATA, Document::class.java) - ?.mapNotNull { it?.asLogVariableData() } ?: listOf() - Log(id, seq, insertedOn, date, type, variableData) - } catch (_: NullPointerException) { - null - } -} +fun Document.asLog() = Log( + getObjectId(Log.ID), + getLong(Log.SEQ), + getLong(Log.INSERTED_ON), + getLong(Log.DATE), + getString(Log.TYPE), + getList(Log.VARIABLE_DATA, Document::class.java).map { it.asLogVariableData() } +) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt index 22c68b798..38905824d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt @@ -22,14 +22,8 @@ data class LogCollectionState( } } -fun Document.asLogCollectionState(): LogCollectionState? { - return try { - val id = getString(LogCollectionState.ID) - val lastLine = getLong(LogCollectionState.LAST_LINE) - val seq = getLong(LogCollectionState.SEQ) - - LogCollectionState(id, lastLine, seq) - } catch (_: NullPointerException) { - null - } -} +fun Document.asLogCollectionState() = LogCollectionState( + getString(LogCollectionState.ID), + getLong(LogCollectionState.LAST_LINE), + getLong(LogCollectionState.SEQ) +) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt index 0f9f0c2ce..33efe6873 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt @@ -21,9 +21,7 @@ data class LogVariableData( } } -fun Document.asLogVariableData(): LogVariableData? { - val header = getString(LogVariableData.HEADER) ?: return null - val description = getString(LogVariableData.DESCRIPTION) ?: return null - - return LogVariableData(header, description) -} +fun Document.asLogVariableData() = LogVariableData( + getString(LogVariableData.HEADER), + getString(LogVariableData.DESCRIPTION) +) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt index 70fbfda8e..2023bb068 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt @@ -31,7 +31,6 @@ data class QuarantineRule( fun from(array: JsonArray): List = array - .mapNotNull { it } .mapNotNull { it as? JsonObject } .map { from(it) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 9787aa7ce..9eb08b25b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -59,7 +59,7 @@ class LogViewerWidget( override fun stop(): Widget { logStorage?.delete() logStorage?.deploymentID()?.let { vertx.undeploy(it) } - consumer?.unregister() + consumer.unregister() return super.stop() } From 1c1134c9294209d81d97b63a40931dfacf943170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Mon, 3 Jan 2022 20:21:22 +0100 Subject: [PATCH 151/226] Change the look of the buttons --- .../types/LogViewerWidget/LogList/LogEntry.js | 47 +++++++++---------- .../types/LogViewerWidget/LogList/styled.js | 30 ++++++------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index 486458c3e..6ee784c82 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { string, number, bool, shape, oneOfType, arrayOf } from 'prop-types'; import { getGridTemplate, highlightText } from './helpers'; -import { AccordionSummary, AccordionDetails } from '@material-ui/core'; +import { AccordionSummary, AccordionDetails, Tooltip } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { GridSchema, @@ -10,12 +10,13 @@ import { VariableGridSchema, HighlightedText, HighlightMark, - VerticalStack, SimilarLogsButtonsContainer, - SimilarLogsButton + FilterSimilarLogsButton, + QuarantineSimilarLogsButton } from './styled'; import { useSelector } from 'react-redux'; import { getIsAuthenticated } from '../../../../../selectors'; +import { FilterList, Schedule } from '@material-ui/icons'; const LogEntry = ({ type, @@ -67,31 +68,29 @@ const LogEntry = ({ - - - - + + - setFilterSimilarLogs(getLastVariableHeader())} - > - Filter similar logs - - {isAuthenticated && ( - - setQuarantineSimilarLogs(getLastVariableHeader()) - } + + setFilterSimilarLogs(getLastVariableHeader())} > - Add similar logs to quarantine - + + + + {isAuthenticated && ( + + + setQuarantineSimilarLogs(getLastVariableHeader()) + } + > + + + )} - + ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index d23901f74..e039e18bf 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -1,7 +1,7 @@ import styled from '@emotion/styled/macro'; import { Virtuoso } from 'react-virtuoso'; import { COLORS } from '../../../../../constants'; -import { Typography, Accordion, Button } from '@material-ui/core'; +import { Typography, Accordion, IconButton } from '@material-ui/core'; import logLevels from '../logLevels'; export const Container = styled.div` @@ -18,16 +18,10 @@ export const Header = styled.div` padding: 0.25em 0; `; -export const VerticalStack = styled.div` - width: 100%; - display: flex; - flex-direction: column; -`; - export const GridSchema = styled.div` width: 100%; display: grid; - grid-template-columns: 70px 150px 1fr; + grid-template-columns: 70px 150px 1fr 56px; padding: 0 10px; `; export const VariableGridSchema = styled.div( @@ -130,13 +124,21 @@ export const HighlightMark = styled.div` `; export const SimilarLogsButtonsContainer = styled.div` - align-self: flex-end; display: flex; - flex-direction: row; - gap: 0.5rem; - margin: 0.25rem 0.5em 0.25em 0; + flex-direction: column; + align-items: flex-end; + margin: 8px 0; + gap: 8px; + box-sizing: border-box; +`; + +export const FilterSimilarLogsButton = styled(IconButton)` + background-color: ${COLORS.LIGHT_SHADE}; `; -export const SimilarLogsButton = styled(Button)` - display: block; +export const QuarantineSimilarLogsButton = styled(IconButton)` + background-color: ${COLORS.RED}; + &:hover { + background-color: ${COLORS.DARK_RED}; + } `; From 0be52c736dfc13fb785e85dd7b0b1592642f39e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Mon, 3 Jan 2022 23:43:29 +0100 Subject: [PATCH 152/226] Fixes for nullable values #445 --- .../kotlin/com/cognifide/cogboard/ssh/SSHClient.kt | 12 +++++++++--- .../widget/type/logviewer/LogViewerWidget.kt | 2 +- .../type/logviewer/logparser/LogParserStrategy.kt | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index bccdac623..38c0bd03d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -11,6 +11,7 @@ import io.vertx.core.buffer.Buffer import io.vertx.core.json.JsonObject import io.vertx.core.logging.Logger import io.vertx.core.logging.LoggerFactory +import java.io.IOException import java.io.InputStream import java.nio.charset.Charset @@ -46,9 +47,14 @@ class SSHClient(private val config: JsonObject) { return null } val (channel, inputStream) = createChannel(command) ?: return null - val response = readResponse(inputStream) - channel.disconnect() - return response + + return try { + readResponse(inputStream) + } catch (e: IOException) { + null + } finally { + channel.disconnect() + } } fun executeAndClose(command: String): String? { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 9787aa7ce..9eb08b25b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -59,7 +59,7 @@ class LogViewerWidget( override fun stop(): Widget { logStorage?.delete() logStorage?.deploymentID()?.let { vertx.undeploy(it) } - consumer?.unregister() + consumer.unregister() return super.stop() } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt index 94c2dfd3f..d79bb269f 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt @@ -4,5 +4,5 @@ import com.cognifide.cogboard.logStorage.model.Log interface LogParserStrategy { val variableFields: List - fun parseLine(line: String): Log? + fun parseLine(line: String): Log } From 45d9af2bd4284a54e278132e5b5d1e6c702418d9 Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 4 Jan 2022 20:39:48 +0100 Subject: [PATCH 153/226] Remove time consts, update test name #441 --- .../LogList/filterByDateSpan.test.js | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js index 89539c9f2..55b129814 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/filterByDateSpan.test.js @@ -9,30 +9,28 @@ const log = { variableData: [] }; -const spanTime10 = moment('2021-12-22T10:00:00.000Z'); -const logTime15 = '2021-12-22T15:00:00.000'; -const spanTime19 = moment('2021-12-22T19:00:00.000Z'); -const spanTime20 = moment('2021-12-22T20:00:00.000Z'); - it('filters out log without date', () => expect( - filterByDateSpan(log, { begin: spanTime10, end: spanTime20 }) + filterByDateSpan(log, { + begin: moment('2021-12-22T10:00:00.000Z'), + end: moment('2021-12-22T20:00:00.000Z') + }) ).toBeFalsy()); -it('should be truthy when no date span is provided', () => +it('should filter in when no date span is provided', () => expect(filterByDateSpan(log, { begin: null, end: null })).toBeTruthy()); it('filters by begin date', () => { expect( filterByDateSpan( - { ...log, date: logTime15 }, - { begin: spanTime10, end: null } + { ...log, date: '2021-12-22T15:00:00.000' }, + { begin: moment('2021-12-22T10:00:00.000Z'), end: null } ) ).toBeTruthy(); expect( filterByDateSpan( { ...log, date: '2021-12-22T20:00:00' }, - { begin: spanTime20, end: null } + { begin: moment('2021-12-22T20:00:00.000Z'), end: null } ) ).toBeFalsy(); }); @@ -40,14 +38,14 @@ it('filters by begin date', () => { it('filters by end date', () => { expect( filterByDateSpan( - { ...log, date: logTime15 }, - { begin: null, end: spanTime20 } + { ...log, date: '2021-12-22T15:00:00.000' }, + { begin: null, end: moment('2021-12-22T20:00:00.000Z') } ) ).toBeTruthy(); expect( filterByDateSpan( - { ...log, date: logTime15 }, - { begin: null, end: spanTime10 } + { ...log, date: '2021-12-22T15:00:00.000' }, + { begin: null, end: moment('2021-12-22T10:00:00.000Z') } ) ).toBeFalsy(); }); @@ -55,14 +53,20 @@ it('filters by end date', () => { it('filters by date span', () => { expect( filterByDateSpan( - { ...log, date: logTime15 }, - { begin: spanTime10, end: spanTime20 } + { ...log, date: '2021-12-22T15:00:00.000' }, + { + begin: moment('2021-12-22T10:00:00.000Z'), + end: moment('2021-12-22T20:00:00.000Z') + } ) ).toBeTruthy(); expect( filterByDateSpan( - { ...log, date: logTime15 }, - { begin: spanTime19, end: spanTime20 } + { ...log, date: '2021-12-22T15:00:00.000' }, + { + begin: moment('2021-12-22T19:00:00.000Z'), + end: moment('2021-12-22T20:00:00.000Z') + } ) ).toBeFalsy(); }); From 1e75830303db941a5a484717cd8ed80126e2f4a7 Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 4 Jan 2022 20:48:41 +0100 Subject: [PATCH 154/226] Refactor filterByDateSpan test code #441 --- .../widgets/types/LogViewerWidget/LogList/helpers.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 81db95ee7..8ac9cbf9d 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -52,8 +52,11 @@ export const filterByRegExp = (log, filters) => end: momentjs-object? */ export const filterByDateSpan = (log, { begin, end }) => { - if (!log.date) { - return !begin && !end; // let empty date through if there is no date span + const isEmptyLogDate = !log.date; + const isNoDateSpanSpecified = !begin && !end; + + if (isEmptyLogDate) { + return isNoDateSpanSpecified; } const date = new Date(log.date); From 57818cf8ad2b16c938b56cb4899c7e9748413d7f Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 4 Jan 2022 21:04:46 +0100 Subject: [PATCH 155/226] Update logLevels tests #441 --- .../cypress/fixtures/logsViewer.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/functional/cypress-tests/cypress/fixtures/logsViewer.js b/functional/cypress-tests/cypress/fixtures/logsViewer.js index c244995e3..5a650ec35 100644 --- a/functional/cypress-tests/cypress/fixtures/logsViewer.js +++ b/functional/cypress-tests/cypress/fixtures/logsViewer.js @@ -1,17 +1,17 @@ +import appLogLevels from '../../../../cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels'; + export const filters = { startsWithA: { label: 'starts with a', - regExp: '^a' + regExp: '^a', }, amet: { label: 'amet', - regExp: 'amet' - } + regExp: 'amet', + }, }; -export const logLevels = [ - { level: 10, value: "debug" }, - { level: 20, value: "info"}, - { level: 30, value: "warning" }, - { level: 40, value: "error" } -] +export const logLevels = Object.keys(appLogLevels).map((key) => ({ + level: appLogLevels[key].level, + value: key, +})); From 1c02c912e807f741ad5ecc437fabbbfbe244633a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 4 Jan 2022 21:17:58 +0100 Subject: [PATCH 156/226] Add similar logs context --- .../types/LogViewerWidget/LogList/LogEntry.js | 13 +++-- .../types/LogViewerWidget/LogList/index.js | 6 +- .../AdvancedFiltersMenu/FilterForm.js | 13 +++-- .../FilterPicker/AdvancedFiltersMenu/index.js | 31 +++-------- .../Toolbar/FilterPicker/index.js | 10 +--- .../Toolbar/QuarantineModal/QuarantineForm.js | 17 +++--- .../Toolbar/QuarantineModal/index.js | 18 +++--- .../types/LogViewerWidget/Toolbar/index.js | 6 +- .../widgets/types/LogViewerWidget/context.js | 3 + .../widgets/types/LogViewerWidget/index.js | 55 ++++++++++--------- 10 files changed, 73 insertions(+), 99 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/context.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index 6ee784c82..a6c64697d 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; import { string, number, bool, shape, oneOfType, arrayOf } from 'prop-types'; import { getGridTemplate, highlightText } from './helpers'; import { AccordionSummary, AccordionDetails, Tooltip } from '@material-ui/core'; @@ -17,6 +17,7 @@ import { import { useSelector } from 'react-redux'; import { getIsAuthenticated } from '../../../../../selectors'; import { FilterList, Schedule } from '@material-ui/icons'; +import { SimilarLogsContext } from '../context'; const LogEntry = ({ type, @@ -24,13 +25,13 @@ const LogEntry = ({ variableData, template, search, - highlight, - setFilterSimilarLogs, - setQuarantineSimilarLogs + highlight }) => { const [expanded, setExpanded] = useState(false); const isAuthenticated = useSelector(getIsAuthenticated); + const similarLogs = useContext(SimilarLogsContext); + const getLastVariableHeader = () => variableData[variableData.length - 1]?.header ?? ''; @@ -73,7 +74,7 @@ const LogEntry = ({ setFilterSimilarLogs(getLastVariableHeader())} + onClick={() => similarLogs.setFilter(getLastVariableHeader())} > @@ -82,7 +83,7 @@ const LogEntry = ({ - setQuarantineSimilarLogs(getLastVariableHeader()) + similarLogs.setQuarantine(getLastVariableHeader()) } > diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index f9b382fa8..7e8022ed2 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -26,9 +26,7 @@ export default function LogList({ template, search, shouldFollowLogs, - handleFollowChange, - setFilterSimilarLogs, - setQuarantineSimilarLogs + handleFollowChange }) { const theme = useTheme(); const scrollerRef = useRef(null); @@ -83,8 +81,6 @@ export default function LogList({ template={template} search={search} highlight={isLogHighlighted(log, search)} - setFilterSimilarLogs={setFilterSimilarLogs} - setQuarantineSimilarLogs={setQuarantineSimilarLogs} /> ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js index 2f88cdd22..71ba23fad 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useContext } from 'react'; import { createValidationSchema } from '../../../../../../validation'; import { useFormData } from '../../../../../../../hooks'; @@ -6,6 +6,7 @@ import { Button } from '@material-ui/core'; import DynamicForm from '../../../../../../DynamicForm'; import { StyledCancelButton } from './styled'; import dialogFields from '../../../../../dialogFields'; +import { SimilarLogsContext } from '../../../context'; const FilterForm = ({ filters, @@ -15,15 +16,15 @@ const FilterForm = ({ filterSimilarLogsState, ...initialFormValues }) => { - const [filterSimilarLogs, setFilterSimilarLogs] = filterSimilarLogsState; + const similarLogs = useContext(SimilarLogsContext); useEffect(() => { - if (filterSimilarLogs) { - setFieldValue(dialogFields.RegExpField.name, filterSimilarLogs); - setFilterSimilarLogs(null); + if (similarLogs.filter) { + setFieldValue(dialogFields.RegExpField.name, similarLogs.filter); + similarLogs.setFilter(null); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filterSimilarLogs]); + }, [similarLogs.filter]); const formFields = ['LabelField', 'RegExpField']; const constraints = { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 0c01069ef..150840d43 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useContext } from 'react'; import { useToggle } from '../../../../../../../hooks'; import { v4 } from 'uuid'; import { getFilters, saveFilters } from '../helpers'; @@ -19,25 +19,19 @@ import EditFilter from './EditFilter'; import DeleteItem from '../../../../../../DeleteItem'; import FilterForm from './FilterForm'; import { StyledExitButton } from './styled'; +import { SimilarLogsContext } from '../../../context'; -const AdvancedFiltersMenu = ({ - widgetLocalStorage, - wid, - quarantine, - filterSimilarLogsState, - quarantineSimilarLogsState -}) => { +const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); - const [filterSimilarLogs] = filterSimilarLogsState; - const [quarantineSimilarLogs] = quarantineSimilarLogsState; + const similarLogs = useContext(SimilarLogsContext); useEffect(() => { - if (filterSimilarLogs || quarantineSimilarLogs) { + if (similarLogs.filter || similarLogs.quarantine) { openDialog(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filterSimilarLogs, quarantineSimilarLogs]); + }, [similarLogs.filter, similarLogs.quarantine]); const filters = getFilters(widgetLocalStorage); @@ -141,18 +135,11 @@ const AdvancedFiltersMenu = ({ largeButton itemName="filter" submitAction={addFilter} - shouldOpen={filterSimilarLogs} + shouldOpen={similarLogs.filter} > - + - + { +const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { const regExpFilters = getFilters(widgetLocalStorage); const logLevel = getLevel(widgetLocalStorage); @@ -103,8 +97,6 @@ const FilterPicker = ({ widgetLocalStorage={widgetLocalStorage} wid={wid} quarantine={quarantine} - filterSimilarLogsState={filterSimilarLogsState} - quarantineSimilarLogsState={quarantineSimilarLogsState} /> ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js index 29375c4ea..367636ce8 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useContext } from 'react'; import { createValidationSchema } from '../../../../../validation'; import { useFormData } from '../../../../../../hooks'; import DynamicForm from '../../../../../DynamicForm'; @@ -6,27 +6,24 @@ import { Button } from '@material-ui/core'; import InfoIcon from '@material-ui/icons/Info'; import { StyledHorizontalContainer, StyledCancelButton } from './styled'; import dialogFields from '../../../../dialogFields'; +import { SimilarLogsContext } from '../../context'; const QuarantineForm = ({ filters, onSubmit, handleCancel, id, - quarantineSimilarLogsState, ...initialFormValues }) => { - const [ - quarantineSimilarLogs, - setQuarantineSimilarLogs - ] = quarantineSimilarLogsState; + const similarLogs = useContext(SimilarLogsContext); useEffect(() => { - if (quarantineSimilarLogs) { - setFieldValue(dialogFields.RegExpField.name, quarantineSimilarLogs); - setQuarantineSimilarLogs(null); + if (similarLogs.quarantine) { + setFieldValue(dialogFields.RegExpField.name, similarLogs.quarantine); + similarLogs.setQuarantine(null); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [quarantineSimilarLogs]); + }, [similarLogs.quarantine]); const formFields = ['LabelField', 'RegExpField', 'ReasonField']; const constraints = { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index b8342c140..666e59a03 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useContext } from 'react'; import { useSelector } from 'react-redux'; import { v4 } from 'uuid'; import { @@ -17,19 +17,20 @@ import AddItem from '../../../../../AddItem'; import QuarantineForm from './QuarantineForm'; import EditQFilter from './EditQFilter'; import DeleteItem from '../../../../../DeleteItem'; +import { SimilarLogsContext } from '../../context'; -const QuarantineModal = ({ wid, quarantine, quarantineSimilarLogsState }) => { +const QuarantineModal = ({ wid, quarantine }) => { const isAuthenticated = useSelector(getIsAuthenticated); const [dialogOpened, openDialog, handleDialogClose] = useToggle(); - const [quarantineSimilarLogs] = quarantineSimilarLogsState; + const similarLogs = useContext(SimilarLogsContext); useEffect(() => { - if (quarantineSimilarLogs) { + if (similarLogs.quarantine) { openDialog(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [quarantineSimilarLogs]); + }, [similarLogs.quarantine]); const handleQuarantineClick = event => { event.stopPropagation(); @@ -132,12 +133,9 @@ const QuarantineModal = ({ wid, quarantine, quarantineSimilarLogsState }) => { largeButton itemName="quarantine" submitAction={addFilter} - shouldOpen={quarantineSimilarLogs} + shouldOpen={similarLogs.quarantine} > - + { const theme = useTheme(); @@ -46,8 +44,6 @@ const Toolbar = ({ widgetLocalStorage={widgetLocalStorage} wid={wid} quarantine={quarantine} - filterSimilarLogsState={filterSimilarLogsState} - quarantineSimilarLogsState={quarantineSimilarLogsState} /> diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/context.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/context.js new file mode 100644 index 000000000..fc6658c24 --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/context.js @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const SimilarLogsContext = createContext(); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index aec82886c..62a8ff6ba 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -8,6 +8,7 @@ import LogList from './LogList'; import { Container } from './styled'; import { getInitialLogs } from '../../../../utils/fetch'; import { joinLogs } from './helpers'; +import { SimilarLogsContext } from './context'; const LogViewerWidget = ({ id }) => { const widgetData = useSelector( @@ -48,36 +49,38 @@ const LogViewerWidget = ({ id }) => { return ( - 0 && - storedLogs[storedLogs.length - 1] - } - filterSimilarLogsState={[filterSimilarLogs, setFilterSimilarLogs]} - quarantineSimilarLogsState={[ - quarantineSimilarLogs, - setQuarantineSimilarLogs - ]} - /> - {storedLogs && ( - + 0 && + storedLogs[storedLogs.length - 1] + } /> - )} + {storedLogs && ( + + )} + ); }; From 3895e4d6bf1f70091ef101bf347546e448c11e66 Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 4 Jan 2022 21:45:27 +0100 Subject: [PATCH 157/226] Revert logLevels change #441 --- .../cypress-tests/cypress/fixtures/logsViewer.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/functional/cypress-tests/cypress/fixtures/logsViewer.js b/functional/cypress-tests/cypress/fixtures/logsViewer.js index 5a650ec35..fde8cbf27 100644 --- a/functional/cypress-tests/cypress/fixtures/logsViewer.js +++ b/functional/cypress-tests/cypress/fixtures/logsViewer.js @@ -1,5 +1,3 @@ -import appLogLevels from '../../../../cogboard-webapp/src/components/widgets/types/LogViewerWidget/logLevels'; - export const filters = { startsWithA: { label: 'starts with a', @@ -11,7 +9,9 @@ export const filters = { }, }; -export const logLevels = Object.keys(appLogLevels).map((key) => ({ - level: appLogLevels[key].level, - value: key, -})); +export const logLevels = [ + { level: 10, value: 'debug' }, + { level: 20, value: 'info' }, + { level: 30, value: 'warning' }, + { level: 40, value: 'error' }, +]; From ab86d9543d91710de4255e0e1d6ca67cba9bc13b Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 4 Jan 2022 22:15:52 +0100 Subject: [PATCH 158/226] Update child input css selector #447 --- .../widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js index cfed1cdc2..33d2d9f62 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js @@ -20,7 +20,7 @@ CustomIconButton.defaultProps = { export const StyledTextField = styled(TextField)` min-width: 5rem; - & input { + & > input { margin-right: 1.8rem; } `; From f29318912dc119cf2ebbe4b2a287d9d8d12015f8 Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Thu, 23 Dec 2021 19:43:21 +0100 Subject: [PATCH 159/226] Quarantine tests added --- .../cogboard/logStorage/QuarantineRuleTest.kt | 43 +++++++++++++++++++ .../cypress-tests/cypress/fixtures/Widgets.js | 9 ++++ .../cypress/integration/log_viewer.js | 31 +++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/QuarantineRuleTest.kt create mode 100644 functional/cypress-tests/cypress/integration/log_viewer.js diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/QuarantineRuleTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/QuarantineRuleTest.kt new file mode 100644 index 000000000..8c866d08c --- /dev/null +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/QuarantineRuleTest.kt @@ -0,0 +1,43 @@ +package com.cognifide.cogboard.logStorage + +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject +import main.kotlin.com.cognifide.cogboard.logStorage.QuarantineRule +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull + +class QuarantineRuleTest { + @Test + fun `Properly parses correct rule`() { + val rule = QuarantineRule.from(JsonObject("{\"label\": \"Example label\", \"reasonField\": \"Reason\", \"regExp\": \"^a\", \"checked\": true}")) + assertEquals("Example label", rule?.label) + assertEquals("Reason", rule?.reasonField) + assertEquals("^a", rule?.regex?.pattern) + assertEquals(true, rule?.enabled) + } + + @Test + fun `Ignores different fields`() { + val rule = QuarantineRule.from(JsonObject("{\"Field1\": \"Example field\", \"SecondField\": 2, \"Another field\": \"Lorem ipsum\", \"Last field\": []}")) + assertNull(rule?.label) + assertNull(rule?.reasonField) + assertNull(rule?.regex) + assertNull(rule?.enabled) + } + + @Test + fun `Empty JSON array creates empty rules array`() { + val rules = QuarantineRule.from(JsonArray("[]")) + assertEquals(rules.size, 0) + } + + @Test + fun `Correctly parser rules in array`() { + val rules = QuarantineRule.from(JsonArray("[{\"label\": \"Example label\", \"reasonField\": \"Reason\", \"regExp\": \"^a\", \"checked\": true}]")) + assertEquals("Example label", rules[0].label) + assertEquals("Reason", rules[0].reasonField) + assertEquals("^a", rules[0].regex.pattern) + assertEquals(true, rules[0].enabled) + } +} \ No newline at end of file diff --git a/functional/cypress-tests/cypress/fixtures/Widgets.js b/functional/cypress-tests/cypress/fixtures/Widgets.js index 73ed399db..2c61a819b 100644 --- a/functional/cypress-tests/cypress/fixtures/Widgets.js +++ b/functional/cypress-tests/cypress/fixtures/Widgets.js @@ -340,5 +340,14 @@ module.exports = { displayDate: true, displayTime: true, textsize: 'h6' + }, + logViewer: { + name: 'Log Viewer', + endpoint: 'endpoint1', + schedulePeriod: 30, + path: '/home/mock/example.txt', + logLinesField: 10, + logFileSizeField: 1, + logRecordExpirationField: 5 } }; diff --git a/functional/cypress-tests/cypress/integration/log_viewer.js b/functional/cypress-tests/cypress/integration/log_viewer.js new file mode 100644 index 000000000..40feb5458 --- /dev/null +++ b/functional/cypress-tests/cypress/integration/log_viewer.js @@ -0,0 +1,31 @@ +import { createWidget } from '../support/widget'; +import { logViewer } from '../fixtures/Widgets'; + +const dashboardName = 'Welcome to Cogboard'; + +describe('Logs Viewer', () => { + let widget; + before(() => { + cy.visit('/'); + cy.login(); + cy.openDrawer(); + cy.chooseDashboard(dashboardName); + cy.clickAddWidgetButton(); + widget = createWidget(logViewer.name).configure(false); + }); + + it('Quarantine button visible while logged in', () => { + cy.get('[data-cy="advanced-filters-button"]').click(); + cy.get('[data-cy="quarantine-show-dialog-button"]').should('exist'); + cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); + }); + + it('Quarantine button invisible while logged out', () => { + cy.get('[data-cy="user-login-logout-icon"]').click(); + cy.get('[data-cy="advanced-filters-button"]').click(); + cy.get('[data-cy="quarantine-show-dialog-button"]').should('not.exist'); + cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); + cy.login(); + widget.remove(); + }); +}); From b38aff0120737cbe91186d3107d5fbbb9b9af652 Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Tue, 4 Jan 2022 14:14:32 +0100 Subject: [PATCH 160/226] Put all LogsViewer tests into one file and correct tests names --- .../cypress-tests/cypress/fixtures/Widgets.js | 9 ------ .../cypress/integration/log_viewer.js | 31 ------------------- .../cypress/integration/logsViewer.js | 21 +++++++++++++ 3 files changed, 21 insertions(+), 40 deletions(-) delete mode 100644 functional/cypress-tests/cypress/integration/log_viewer.js diff --git a/functional/cypress-tests/cypress/fixtures/Widgets.js b/functional/cypress-tests/cypress/fixtures/Widgets.js index 2c61a819b..73ed399db 100644 --- a/functional/cypress-tests/cypress/fixtures/Widgets.js +++ b/functional/cypress-tests/cypress/fixtures/Widgets.js @@ -340,14 +340,5 @@ module.exports = { displayDate: true, displayTime: true, textsize: 'h6' - }, - logViewer: { - name: 'Log Viewer', - endpoint: 'endpoint1', - schedulePeriod: 30, - path: '/home/mock/example.txt', - logLinesField: 10, - logFileSizeField: 1, - logRecordExpirationField: 5 } }; diff --git a/functional/cypress-tests/cypress/integration/log_viewer.js b/functional/cypress-tests/cypress/integration/log_viewer.js deleted file mode 100644 index 40feb5458..000000000 --- a/functional/cypress-tests/cypress/integration/log_viewer.js +++ /dev/null @@ -1,31 +0,0 @@ -import { createWidget } from '../support/widget'; -import { logViewer } from '../fixtures/Widgets'; - -const dashboardName = 'Welcome to Cogboard'; - -describe('Logs Viewer', () => { - let widget; - before(() => { - cy.visit('/'); - cy.login(); - cy.openDrawer(); - cy.chooseDashboard(dashboardName); - cy.clickAddWidgetButton(); - widget = createWidget(logViewer.name).configure(false); - }); - - it('Quarantine button visible while logged in', () => { - cy.get('[data-cy="advanced-filters-button"]').click(); - cy.get('[data-cy="quarantine-show-dialog-button"]').should('exist'); - cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); - }); - - it('Quarantine button invisible while logged out', () => { - cy.get('[data-cy="user-login-logout-icon"]').click(); - cy.get('[data-cy="advanced-filters-button"]').click(); - cy.get('[data-cy="quarantine-show-dialog-button"]').should('not.exist'); - cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); - cy.login(); - widget.remove(); - }); -}); diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 893214216..3b18fcd02 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -33,6 +33,10 @@ describe('Logs Viewer', () => { }); }); + beforeEach(() => { + cy.viewport(1920, 1080); + }); + describe('Filters', () => { it('opens advanced filters modal', () => { openAdvancedMenu(); @@ -146,6 +150,23 @@ describe('Logs Viewer', () => { '' ); cy.get('[data-cy="log-entry"]').should('exist'); + }); + }); + + describe('Quarantine', () => { + it('should allow logged in users to click quarantine button', () => { + cy.get('[data-cy="advanced-filters-button"]').click(); + cy.get('[data-cy="quarantine-show-dialog-button"]').should('exist'); + cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); + }); + + it('should not allow logged out users to click quarantine button', () => { + cy.get('[data-cy="user-login-logout-icon"]').click(); + cy.get('[data-cy="advanced-filters-button"]').click(); + cy.get('[data-cy="quarantine-show-dialog-button"]').should('not.exist'); + cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); + cy.login(); + widget.remove(); }); }); }); From 8fb5fc944d756be9fc1c38c9679f1194a84ab8d9 Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 4 Jan 2022 23:27:44 +0100 Subject: [PATCH 161/226] Update advanced filters button #447 --- .../Toolbar/FilterPicker/AdvancedFiltersMenu/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 4f7a03e9f..5f1ecf171 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -13,7 +13,7 @@ import { Switch, Tooltip } from '@material-ui/core'; -import AddIcon from '@material-ui/icons/Add'; +import AdvancedIcon from '@material-ui/icons/FilterList'; import AppDialog from '../../../../../../AppDialog'; import AddItem from '../../../../../../AddItem'; import EditFilter from './EditFilter'; @@ -102,7 +102,7 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { tooltip="Advanced" enabled={dialogOpened} onClick={openDialog} - Icon={AddIcon} + Icon={AdvancedIcon} data-cy="advanced-filters-button" /> Date: Tue, 4 Jan 2022 23:40:03 +0100 Subject: [PATCH 162/226] Tests fix --- .../cogboard/logStorage/{ => model}/QuarantineRuleTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/{ => model}/QuarantineRuleTest.kt (93%) diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/QuarantineRuleTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt similarity index 93% rename from cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/QuarantineRuleTest.kt rename to cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt index 8c866d08c..a5c1a8ccd 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/QuarantineRuleTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt @@ -1,8 +1,7 @@ -package com.cognifide.cogboard.logStorage +package com.cognifide.cogboard.logStorage.model import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject -import main.kotlin.com.cognifide.cogboard.logStorage.QuarantineRule import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull From 5e156f491c7f8243918d6ad347b6ef531049dfbf Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Mon, 27 Dec 2021 18:53:57 +0100 Subject: [PATCH 163/226] Seaarchbar tests added --- .../types/LogViewerWidget/LogList/LogEntry.js | 2 +- .../LogList/highlightning.test.js | 34 +++++++++++ .../Toolbar/SearchInput/index.js | 7 ++- .../cypress/integration/log_viewer.js | 57 +++++++++++++++++++ .../cypress/support/widgetDynamicTab.js | 3 + 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js create mode 100644 functional/cypress-tests/cypress/integration/log_viewer.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index b8107c3e1..c7ac89f26 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -48,7 +48,7 @@ const LogEntry = ({ onClick={() => setExpanded(!expanded)} expandIcon={expanded && } > - {highlight && } + {highlight && } {type?.toUpperCase()} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js new file mode 100644 index 000000000..1b7e865ec --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js @@ -0,0 +1,34 @@ +import { isLogHighlighted } from './helpers'; + +const logHeader = { + _id: '61b4c753bc103d391657c49c', + seq: 228, + insertedOn: 1639237459, + date: '2021-12-11T15:44:00', + type: 'DEBUG', + variableData: null +}; + +it('log highlighting', () => { + const variableData = [ + { + header: 'FelixStartLevel', + description: 'No description' + }, + { + header: 'id velit. vel pretium. ipsum suscipit', + description: 'No message description' + } + ]; + const log = { ...logHeader, variableData }; + + expect(isLogHighlighted(log, 'eli')).toEqual(true); + expect(isLogHighlighted(log, 'ELI')).toEqual(true); + expect(isLogHighlighted(log, 'id velit')).toEqual(true); + expect(isLogHighlighted(log, 'cipit')).toEqual(true); + expect(isLogHighlighted(log, 'val')).toEqual(false); + expect(isLogHighlighted(log, 'FelixStartLevel')).toEqual(true); + expect(isLogHighlighted(log, 'description')).toEqual(true); + expect(isLogHighlighted(log, 'message')).toEqual(true); + expect(isLogHighlighted(log, '')).toBeFalsy(); +}); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index a677c66f2..4db10ac5c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -29,9 +29,14 @@ const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { label="Search" value={searchBoxValue} onChange={handleChange} + data-cy="search-input-field" /> - {enoughLetters ? : } + {enoughLetters ? ( + + ) : ( + + )} ); diff --git a/functional/cypress-tests/cypress/integration/log_viewer.js b/functional/cypress-tests/cypress/integration/log_viewer.js new file mode 100644 index 000000000..32817c6a4 --- /dev/null +++ b/functional/cypress-tests/cypress/integration/log_viewer.js @@ -0,0 +1,57 @@ +import { logsViewer } from '../fixtures/Widgets'; +import { createWidget } from '../support/widget'; + +const dashboardName = 'Welcome to Cogboard'; + +describe('Logs Viewer', () => { + let widget; + + before(() => { + cy.visit('/'); + cy.login(); + cy.openDrawer(); + cy.chooseDashboard(dashboardName); + cy.clickAddWidgetButton(); + widget = createWidget(logsViewer.name).configure(false, { + cols: 8, + rows: 2 + }); + }); + + beforeEach(() => { + cy.viewport(1920, 1080); + }); + + describe('Searchbar', () => { + it('Search icon is shown while there are less than 3 letters in input', () => { + cy.get('[data-cy="search-icon"]').should('exist'); + cy.get('[data-cy="search-input-field"]').type('l'); + cy.get('[data-cy="search-icon"]').should('exist'); + cy.get('[data-cy="search-input-field"]').type('o'); + cy.get('[data-cy="search-icon"]').should('exist'); + cy.get('[data-cy="search-input-field"]').type('r'); + cy.get('[data-cy="search-icon"]').should('not.exist'); + }); + + it('Close icon is shown when at least 3 letters i input', () => { + cy.get('[data-cy="close-icon"]').should('exist'); + }); + + it('All highlighted logs have text from search input', () => { + cy.get('[data-cy="highlight-mark"]').each(mark => { + cy.wrap(mark) + .closest('[data-cy="log-entry"]') + .contains(new RegExp('lor', 'gi')) + .should('exist'); + }); + }); + + it('Clearing search input works properly', () => { + cy.get('[data-cy="close-icon"]').click(); + cy.get('[data-cy="search-input-field"]') + .invoke('val') + .should('be.empty'); + }); + }); +}); +//npx cypress run diff --git a/functional/cypress-tests/cypress/support/widgetDynamicTab.js b/functional/cypress-tests/cypress/support/widgetDynamicTab.js index a45bc0291..4d37b404c 100644 --- a/functional/cypress-tests/cypress/support/widgetDynamicTab.js +++ b/functional/cypress-tests/cypress/support/widgetDynamicTab.js @@ -91,8 +91,11 @@ export function fillLogsViewer() { cy.get(`[data-value="${Widgets.logsViewer.endpoint}"]`).click(); cy.fillSchedulePeriod(Widgets.logsViewer.schedulePeriod); cy.get('[data-cy="widget-form-path-input"]').type(Widgets.logsViewer.path); +<<<<<<< HEAD cy.get('[data-cy="widget-form-log-parser-field-input"]').click(); cy.get(`[data-value="${Widgets.logsViewer.parserType}"]`).click(); +======= +>>>>>>> Seaarchbar tests added } export function fillServiceCheck() { From 3278d1faa2a1a9fe56feb007f87e7741cd0de82a Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Tue, 4 Jan 2022 14:59:53 +0100 Subject: [PATCH 164/226] Fixes after code review --- .../LogList/highlightning.test.js | 24 ++++---- .../cypress/integration/log_viewer.js | 57 ------------------- .../cypress/integration/logsViewer.js | 36 ++++++++++++ 3 files changed, 50 insertions(+), 67 deletions(-) delete mode 100644 functional/cypress-tests/cypress/integration/log_viewer.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js index 1b7e865ec..42d1c16d8 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/highlightning.test.js @@ -9,7 +9,7 @@ const logHeader = { variableData: null }; -it('log highlighting', () => { +describe('log highlighting', () => { const variableData = [ { header: 'FelixStartLevel', @@ -22,13 +22,17 @@ it('log highlighting', () => { ]; const log = { ...logHeader, variableData }; - expect(isLogHighlighted(log, 'eli')).toEqual(true); - expect(isLogHighlighted(log, 'ELI')).toEqual(true); - expect(isLogHighlighted(log, 'id velit')).toEqual(true); - expect(isLogHighlighted(log, 'cipit')).toEqual(true); - expect(isLogHighlighted(log, 'val')).toEqual(false); - expect(isLogHighlighted(log, 'FelixStartLevel')).toEqual(true); - expect(isLogHighlighted(log, 'description')).toEqual(true); - expect(isLogHighlighted(log, 'message')).toEqual(true); - expect(isLogHighlighted(log, '')).toBeFalsy(); + it('should highlight logs', () => { + expect(isLogHighlighted(log, 'eli')).toEqual(true); + expect(isLogHighlighted(log, 'ELI')).toEqual(true); + expect(isLogHighlighted(log, 'id velit')).toEqual(true); + expect(isLogHighlighted(log, 'cipit')).toEqual(true); + expect(isLogHighlighted(log, 'FelixStartLevel')).toEqual(true); + expect(isLogHighlighted(log, 'description')).toEqual(true); + expect(isLogHighlighted(log, 'message')).toEqual(true); + }); + it('should not highlight logs', () => { + expect(isLogHighlighted(log, 'val')).toEqual(false); + expect(isLogHighlighted(log, '')).toBeFalsy(); + }); }); diff --git a/functional/cypress-tests/cypress/integration/log_viewer.js b/functional/cypress-tests/cypress/integration/log_viewer.js deleted file mode 100644 index 32817c6a4..000000000 --- a/functional/cypress-tests/cypress/integration/log_viewer.js +++ /dev/null @@ -1,57 +0,0 @@ -import { logsViewer } from '../fixtures/Widgets'; -import { createWidget } from '../support/widget'; - -const dashboardName = 'Welcome to Cogboard'; - -describe('Logs Viewer', () => { - let widget; - - before(() => { - cy.visit('/'); - cy.login(); - cy.openDrawer(); - cy.chooseDashboard(dashboardName); - cy.clickAddWidgetButton(); - widget = createWidget(logsViewer.name).configure(false, { - cols: 8, - rows: 2 - }); - }); - - beforeEach(() => { - cy.viewport(1920, 1080); - }); - - describe('Searchbar', () => { - it('Search icon is shown while there are less than 3 letters in input', () => { - cy.get('[data-cy="search-icon"]').should('exist'); - cy.get('[data-cy="search-input-field"]').type('l'); - cy.get('[data-cy="search-icon"]').should('exist'); - cy.get('[data-cy="search-input-field"]').type('o'); - cy.get('[data-cy="search-icon"]').should('exist'); - cy.get('[data-cy="search-input-field"]').type('r'); - cy.get('[data-cy="search-icon"]').should('not.exist'); - }); - - it('Close icon is shown when at least 3 letters i input', () => { - cy.get('[data-cy="close-icon"]').should('exist'); - }); - - it('All highlighted logs have text from search input', () => { - cy.get('[data-cy="highlight-mark"]').each(mark => { - cy.wrap(mark) - .closest('[data-cy="log-entry"]') - .contains(new RegExp('lor', 'gi')) - .should('exist'); - }); - }); - - it('Clearing search input works properly', () => { - cy.get('[data-cy="close-icon"]').click(); - cy.get('[data-cy="search-input-field"]') - .invoke('val') - .should('be.empty'); - }); - }); -}); -//npx cypress run diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index 893214216..c7af366a7 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -33,6 +33,10 @@ describe('Logs Viewer', () => { }); }); + beforeEach(() => { + cy.viewport(1920, 1080); + }); + describe('Filters', () => { it('opens advanced filters modal', () => { openAdvancedMenu(); @@ -148,4 +152,36 @@ describe('Logs Viewer', () => { cy.get('[data-cy="log-entry"]').should('exist'); }); }); + + describe('Searchbar', () => { + it('shows search icon while there are less than 3 letters in input', () => { + cy.get('[data-cy="search-icon"]').should('exist'); + cy.get('[data-cy="search-input-field"]').type('l'); + cy.get('[data-cy="search-icon"]').should('exist'); + cy.get('[data-cy="search-input-field"]').type('o'); + cy.get('[data-cy="search-icon"]').should('exist'); + cy.get('[data-cy="search-input-field"]').type('r'); + cy.get('[data-cy="search-icon"]').should('not.exist'); + }); + + it('it shows close icon when at least 3 letters are in input', () => { + cy.get('[data-cy="close-icon"]').should('exist'); + }); + + it('shows highlight mark on logs fitting expression', () => { + cy.get('[data-cy="highlight-mark"]').each(mark => { + cy.wrap(mark) + .closest('[data-cy="log-entry"]') + .contains(new RegExp('lor', 'gi')) + .should('exist'); + }); + }); + + it('clears input after clicking close icon', () => { + cy.get('[data-cy="close-icon"]').click(); + cy.get('[data-cy="search-input-field"]') + .invoke('val') + .should('be.empty'); + }); + }); }); From c7b1a24dcd467fc76d9a2f37c2b1e871b6fb6607 Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Wed, 5 Jan 2022 00:34:32 +0100 Subject: [PATCH 165/226] Fix in widgetDynamicTab --- functional/cypress-tests/cypress/support/widgetDynamicTab.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/functional/cypress-tests/cypress/support/widgetDynamicTab.js b/functional/cypress-tests/cypress/support/widgetDynamicTab.js index 4d37b404c..a45bc0291 100644 --- a/functional/cypress-tests/cypress/support/widgetDynamicTab.js +++ b/functional/cypress-tests/cypress/support/widgetDynamicTab.js @@ -91,11 +91,8 @@ export function fillLogsViewer() { cy.get(`[data-value="${Widgets.logsViewer.endpoint}"]`).click(); cy.fillSchedulePeriod(Widgets.logsViewer.schedulePeriod); cy.get('[data-cy="widget-form-path-input"]').type(Widgets.logsViewer.path); -<<<<<<< HEAD cy.get('[data-cy="widget-form-log-parser-field-input"]').click(); cy.get(`[data-value="${Widgets.logsViewer.parserType}"]`).click(); -======= ->>>>>>> Seaarchbar tests added } export function fillServiceCheck() { From adbd9945a8b35a7011f45343962067803492b235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Wed, 5 Jan 2022 00:33:45 +0100 Subject: [PATCH 166/226] Added changes to generation of cogboard-compose.yml #428 --- cogboard-local-compose.yml | 8 ++++---- gradle.properties | 6 +++++- gradle/prepareCogboardCompose.gradle.kts | 8 ++++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 329c2d671..f38a8398d 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -26,8 +26,8 @@ services: image: "cogboard/cogboard-app:${COGBOARD_VERSION}" environment: - COGBOARD_VERSION=${COGBOARD_VERSION} - - MONGO_USERNAME=root - - MONGO_PASSWORD=root + - MONGO_USERNAME=${MONGO_USERNAME} + - MONGO_PASSWORD=${MONGO_PASSWORD} - MONGO_HOST=mongo - MONGO_PORT=27017 volumes: @@ -41,8 +41,8 @@ services: image: mongo:4 restart: always environment: - MONGO_INITDB_ROOT_USERNAME: "root" - MONGO_INITDB_ROOT_PASSWORD: "root" + MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} MONGO_INITDB_DATABASE: "logs" volumes: - "./mnt/mongo:/data/db" diff --git a/gradle.properties b/gradle.properties index 5839e4642..6ca09415c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,4 +16,8 @@ docker.app.container.name=cogboard ws.port=9001 #App -app.port=8092 \ No newline at end of file +app.port=8092 + +# Mongo +mongo.user=s_root +mongo.password=root \ No newline at end of file diff --git a/gradle/prepareCogboardCompose.gradle.kts b/gradle/prepareCogboardCompose.gradle.kts index ca1b2e443..0e12fd6cd 100644 --- a/gradle/prepareCogboardCompose.gradle.kts +++ b/gradle/prepareCogboardCompose.gradle.kts @@ -9,6 +9,8 @@ fun createComposeFile() { val composeFilePath = "$rootDir/cogboard-compose.yml" logger.lifecycle(">> createZip >> Creating $composeFilePath") + val user = project.property("mongo.user") + val password = project.property("mongo.password") File(composeFilePath).writeText("""version: "3.7" services: @@ -16,6 +18,8 @@ services: image: "cogboard/cogboard-app:$currentVersion" environment: - COGBOARD_VERSION=$currentVersion + - MONGO_USERNAME=$user + - MONGO_PASSWORD=$password volumes: - "./mnt:/data" @@ -23,8 +27,8 @@ services: image: mongo restart: always environment: - MONGO_INITDB_ROOT_USERNAME: "root" - MONGO_INITDB_ROOT_PASSWORD: "root" + MONGO_INITDB_ROOT_USERNAME: "$user" + MONGO_INITDB_ROOT_PASSWORD: "$password" MONGO_INITDB_DATABASE: "logs" volumes: - "./mnt/mongo:/data/db" From b8e76d6e49c1c362196e9d842f62b82542635a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 28 Dec 2021 23:50:38 +0100 Subject: [PATCH 167/226] Fix bugs and improve the code --- .../cogboard/logStorage/LogController.kt | 4 +- .../cogboard/logStorage/LogStorage.kt | 61 ++++++++----------- .../cogboard/logStorage/model/Log.kt | 30 +++++---- ...Configuration.kt => LogCollectionState.kt} | 22 +++---- .../logStorage/model/LogVariableData.kt | 12 ++-- .../widget/type/logviewer/LogViewerWidget.kt | 4 +- .../logparser/MockLogParserStrategy.kt | 15 ++++- .../logparser/MockLogParserStrategyTest.kt | 2 +- 8 files changed, 74 insertions(+), 76 deletions(-) rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/{LogCollectionConfiguration.kt => LogCollectionState.kt} (51%) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt index 0e6c489a8..f0a66b323 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt @@ -2,6 +2,7 @@ package com.cognifide.cogboard.logStorage import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.logStorage.model.Log +import com.cognifide.cogboard.logStorage.model.asLog import com.cognifide.cogboard.storage.VolumeStorageFactory.boards import com.cognifide.cogboard.widget.type.logviewer.LogViewerWidget import com.mongodb.client.model.Sorts @@ -41,8 +42,7 @@ class LogController : RoutingHandlerFactory { ?.find() ?.sort(Sorts.descending(Log.SEQ)) ?.limit(logLines) - ?.mapNotNull { it } - ?.mapNotNull { Log.from(it) } + ?.mapNotNull { it?.asLog() } ?.sortedBy { it.seq } ?: emptyList() } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index d68c1cd93..96c3ed653 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -1,5 +1,11 @@ package com.cognifide.cogboard.logStorage +import com.cognifide.cogboard.logStorage.model.Log +import com.cognifide.cogboard.logStorage.model.LogCollectionState +import com.cognifide.cogboard.logStorage.model.asLogCollectionState +import com.cognifide.cogboard.logStorage.model.LogStorageConfiguration +import com.cognifide.cogboard.logStorage.model.LogVariableData +import com.cognifide.cogboard.logStorage.model.QuarantineRule import com.cognifide.cogboard.widget.connectionStrategy.ConnectionStrategy import com.cognifide.cogboard.widget.type.logviewer.logparser.LogParserStrategy import com.mongodb.client.MongoClient @@ -21,11 +27,6 @@ import io.vertx.core.json.JsonObject import io.vertx.core.logging.Logger import io.vertx.core.logging.LoggerFactory import org.bson.Document -import com.cognifide.cogboard.logStorage.model.Log -import com.cognifide.cogboard.logStorage.model.LogStorageConfiguration -import com.cognifide.cogboard.logStorage.model.LogVariableData -import com.cognifide.cogboard.logStorage.model.QuarantineRule -import com.cognifide.cogboard.logStorage.model.LogCollectionConfiguration import java.net.URI import java.time.Instant @@ -52,24 +53,24 @@ class LogStorage( // Storage configuration /** Returns a logs collection configuration associated with this widget (if present). */ - private val collectionConfiguration: LogCollectionConfiguration? - get() = configCollection + private val collectionState: LogCollectionState? + get() = stateCollection ?.find(eq(Log.ID, config.id)) ?.first() - ?.let { LogCollectionConfiguration.from(it) } + ?.let { it.asLogCollectionState() } - /** Saves or deletes a logs collection [configuration] associated with this widget. */ - private fun saveConfiguration(configuration: LogCollectionConfiguration?) { - if (configuration != null) { + /** Saves or deletes a logs collection [state] associated with this widget. */ + private fun saveState(state: LogCollectionState?) { + if (state != null) { val options = ReplaceOptions().upsert(true) - configCollection + stateCollection ?.replaceOne( - eq(LogCollectionConfiguration.ID, config.id), - configuration.toDocument(), + eq(LogCollectionState.ID, config.id), + state.toDocument(), options ) } else { - configCollection?.deleteOne((eq(LogCollectionConfiguration.ID, config.id))) + stateCollection?.deleteOne((eq(LogCollectionState.ID, config.id))) } } @@ -161,7 +162,6 @@ class LogStorage( .mapNotNull { parserStrategy.parseLine(it) } .toMutableList() - // Filter the logs by quarantine rules filter(logs) return logs @@ -175,7 +175,7 @@ class LogStorage( logs.forEach { it.seq = sequence - sequence += 1 + sequence++ } logsCollection?.insertMany(logs.map { it.toDocument() }) @@ -183,20 +183,15 @@ class LogStorage( /** Checks how many logs to download, downloads them and saves them to the database. */ private fun downloadLogs(): List { - // Get the current settings - val storageConfig = collectionConfiguration - var lastLine = storageConfig?.lastLine ?: 0 - var seq = storageConfig?.seq ?: 0 + var lastLine = collectionState?.lastLine ?: 0 + var seq = collectionState?.seq ?: 0 var newLogs: List = emptyList() - // Get the number of lines in the file val fileLineCount = connection.getNumberOfLines() ?: 0 if (fileLineCount > 0 && fileLineCount > lastLine) { - // Download new logs and append them newLogs = downloadFilterLogs(lastLine) } else if (fileLineCount in 1 until lastLine) { - // Remove all logs and download from the beginning deleteAllLogs() seq = 0 lastLine = 0 @@ -207,8 +202,7 @@ class LogStorage( lastLine += newLogs.size seq += newLogs.size - // Save the new configuration - saveConfiguration(LogCollectionConfiguration(config.id, lastLine, seq)) + saveState(LogCollectionState(config.id, lastLine, seq)) return newLogs } @@ -226,15 +220,12 @@ class LogStorage( var insertedLogs: List = emptyList() if (fetchNewLogs) { - // Download new logs insertedLogs = downloadLogs() - // Delete unnecessary logs deleteOldLogs() deleteSpaceConsumingLogs() } - // Fetch the logs from the database and send them back val response = prepareResponse(insertedLogs) vertx?.eventBus()?.send(config.eventBusAddress, response) } @@ -242,12 +233,12 @@ class LogStorage( /** Deletes all data associated with the widget. */ fun delete() { deleteAllLogs() - saveConfiguration(null) + saveState(null) } companion object { private const val DATABASE_NAME: String = "logs" - private const val CONFIGURATION_COLLECTION_NAME: String = "config" + private const val STATE_COLLECTION_NAME: String = "config" private const val STATS_COMMAND: String = "collStats" private const val STATS_SIZE: String = "size" private const val MB_TO_BYTES: Long = 1024L * 1024L @@ -283,13 +274,13 @@ class LogStorage( return null } - /** Returns a database for storing logs and collection configurations. */ + /** Returns a database for storing logs and collection states. */ val database: MongoDatabase? get() = client?.getDatabase(DATABASE_NAME) - /** Returns a configuration collection. */ - val configCollection: MongoCollection? - get() = database?.getCollection(CONFIGURATION_COLLECTION_NAME) + /** Returns a state collection. */ + val stateCollection: MongoCollection? + get() = database?.getCollection(STATE_COLLECTION_NAME) private val LOGGER: Logger = LoggerFactory.getLogger(LogStorage::class.java) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt index d9ec34a06..f6c30b4d8 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt @@ -42,23 +42,21 @@ data class Log( const val DATE = "date" const val TYPE = "type" const val VARIABLE_DATA = "variableData" + } +} - fun from(document: Document): Log? { - return try { - val id = document.getObjectId(ID)!! - val seq = document.getLong(SEQ)!! - val insertedOn = document.getLong(INSERTED_ON)!! - val date = document.getLong(DATE)!! - val type = document.getString(TYPE)!! +fun Document.asLog(): Log? { + return try { + val id = getObjectId(Log.ID)!! + val seq = getLong(Log.SEQ)!! + val insertedOn = getLong(Log.INSERTED_ON)!! + val date = getLong(Log.DATE)!! + val type = getString(Log.TYPE)!! - val variableData = document - .getList(VARIABLE_DATA, Document::class.java) - ?.mapNotNull { it } - ?.mapNotNull { LogVariableData.from(it) } ?: listOf() - Log(id, seq, insertedOn, date, type, variableData) - } catch (_: NullPointerException) { - null - } - } + val variableData = getList(Log.VARIABLE_DATA, Document::class.java) + ?.mapNotNull { it?.asLogVariableData() } ?: listOf() + Log(id, seq, insertedOn, date, type, variableData) + } catch (_: NullPointerException) { + null } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt similarity index 51% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt index f851da02b..22c68b798 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionConfiguration.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt @@ -2,7 +2,7 @@ package com.cognifide.cogboard.logStorage.model import org.bson.Document -data class LogCollectionConfiguration( +data class LogCollectionState( var id: String, var lastLine: Long, var seq: Long @@ -19,17 +19,17 @@ data class LogCollectionConfiguration( const val ID = "_id" const val LAST_LINE = "lastLine" const val SEQ = "seq" + } +} - fun from(document: Document): LogCollectionConfiguration? { - return try { - val id = document.getString(ID) - val lastLine = document.getLong(LAST_LINE) - val seq = document.getLong(SEQ) +fun Document.asLogCollectionState(): LogCollectionState? { + return try { + val id = getString(LogCollectionState.ID) + val lastLine = getLong(LogCollectionState.LAST_LINE) + val seq = getLong(LogCollectionState.SEQ) - LogCollectionConfiguration(id, lastLine, seq) - } catch (_: NullPointerException) { - null - } - } + LogCollectionState(id, lastLine, seq) + } catch (_: NullPointerException) { + null } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt index f285fd815..0f9f0c2ce 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt @@ -18,12 +18,12 @@ data class LogVariableData( companion object { const val HEADER = "header" const val DESCRIPTION = "description" + } +} - fun from(document: Document): LogVariableData? { - val header = document.getString(HEADER) ?: return null - val description = document.getString(DESCRIPTION) ?: return null +fun Document.asLogVariableData(): LogVariableData? { + val header = getString(LogVariableData.HEADER) ?: return null + val description = getString(LogVariableData.DESCRIPTION) ?: return null - return LogVariableData(header, description) - } - } + return LogVariableData(header, description) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 68b803d88..9787aa7ce 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -24,7 +24,7 @@ class LogViewerWidget( ) : BaseWidget(vertx, config, serv) { private val contentRepository: ContentRepository = ContentRepository.DEFAULT private val address = config.endpointProp(Props.URL) - private var consumer: MessageConsumer? = null + private lateinit var consumer: MessageConsumer private val connectionStrategy: ConnectionStrategy? = determineConnectionStrategy() private val logStorage: LogStorage? = connectionStrategy?.let { LogStorage( @@ -84,7 +84,7 @@ class LogViewerWidget( send(logs) } - /** Gets the quarantine rules from the */ + /** Gets the quarantine rules from the content repository. */ private val rules: List get() = contentRepository .get(id) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt index fb126323c..b8235b181 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt @@ -12,8 +12,8 @@ class MockLogParserStrategy : LogParserStrategy { private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""" .toRegex() - override fun parseLine(line: String): Log? { - val groups = regex.matchEntire(line.trim())?.groups ?: return null + override fun parseLine(line: String): Log { + val groups = regex.matchEntire(line.trim())?.groups ?: return makeParsingErrorLog(line) try { val date = LocalDateTime @@ -30,7 +30,7 @@ class MockLogParserStrategy : LogParserStrategy { return Log(date = date, type = type, variableData = variableData) } catch (_: NullPointerException) { - return null + return makeParsingErrorLog(line) } } @@ -40,5 +40,14 @@ class MockLogParserStrategy : LogParserStrategy { private const val TYPE = "type" private const val PROVIDER = "provider" private const val MESSAGE = "message" + + private fun makeParsingErrorLog(line: String): Log = Log( + date = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC), + type = "ERROR", + variableData = listOf( + LogVariableData("MockLogParserStrategy", "No description"), + LogVariableData("Cannot parse a log", "Line causing the error: $line") + ) + ) } } diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt index 2acc66b25..2fd8039c3 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt @@ -11,7 +11,7 @@ class MockLogParserStrategyTest { fun parseSampleLog() { assert(parser.variableFields == listOf("Provider", "Message")) - val output = parser.parseLine(sampleLog) ?: throw AssertionError("Parsed log should not be null") + val output = parser.parseLine(sampleLog) assert(output.type == "DEBUG") assert(output.date == 1636238425L) From 86e2bf8c2b3d7aabcd6767e43bc6310a1a6fd855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 29 Dec 2021 22:40:59 +0100 Subject: [PATCH 168/226] Add an empty quarantine rule --- .../cognifide/cogboard/logStorage/model/QuarantineRule.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt index 92773d9cc..70fbfda8e 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt @@ -14,8 +14,9 @@ data class QuarantineRule( private const val REASON = "reasonField" private const val REGEX = "regExp" private const val ENABLED = "checked" + private val default = QuarantineRule("Default", "", "(?!x)x".toRegex(), false) - fun from(json: JsonObject): QuarantineRule? { + fun from(json: JsonObject): QuarantineRule { return try { QuarantineRule( json.getString(LABEL)!!, @@ -24,7 +25,7 @@ data class QuarantineRule( json.getBoolean(ENABLED)!! ) } catch (_: NullPointerException) { - null + default } } @@ -32,6 +33,6 @@ data class QuarantineRule( array .mapNotNull { it } .mapNotNull { it as? JsonObject } - .mapNotNull { from(it) } + .map { from(it) } } } From 60201b3850999a707e459f14d8608ba68abcfcc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Mon, 3 Jan 2022 23:43:29 +0100 Subject: [PATCH 169/226] Fixes for nullable values #445 --- .../kotlin/com/cognifide/cogboard/ssh/SSHClient.kt | 12 +++++++++--- .../widget/type/logviewer/LogViewerWidget.kt | 2 +- .../type/logviewer/logparser/LogParserStrategy.kt | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index bccdac623..38c0bd03d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -11,6 +11,7 @@ import io.vertx.core.buffer.Buffer import io.vertx.core.json.JsonObject import io.vertx.core.logging.Logger import io.vertx.core.logging.LoggerFactory +import java.io.IOException import java.io.InputStream import java.nio.charset.Charset @@ -46,9 +47,14 @@ class SSHClient(private val config: JsonObject) { return null } val (channel, inputStream) = createChannel(command) ?: return null - val response = readResponse(inputStream) - channel.disconnect() - return response + + return try { + readResponse(inputStream) + } catch (e: IOException) { + null + } finally { + channel.disconnect() + } } fun executeAndClose(command: String): String? { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 9787aa7ce..9eb08b25b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -59,7 +59,7 @@ class LogViewerWidget( override fun stop(): Widget { logStorage?.delete() logStorage?.deploymentID()?.let { vertx.undeploy(it) } - consumer?.unregister() + consumer.unregister() return super.stop() } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt index 94c2dfd3f..d79bb269f 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategy.kt @@ -4,5 +4,5 @@ import com.cognifide.cogboard.logStorage.model.Log interface LogParserStrategy { val variableFields: List - fun parseLine(line: String): Log? + fun parseLine(line: String): Log } From 01c62307dd5721375df8d55f6204b37f95335ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 2 Jan 2022 23:45:51 +0100 Subject: [PATCH 170/226] Remove some nullable fields --- .../cogboard/logStorage/LogController.kt | 23 +++-- .../cogboard/logStorage/LogStorage.kt | 85 ++++++++----------- .../cogboard/logStorage/model/Log.kt | 23 ++--- .../logStorage/model/LogCollectionState.kt | 16 ++-- .../logStorage/model/LogVariableData.kt | 10 +-- .../logStorage/model/QuarantineRule.kt | 1 - 6 files changed, 62 insertions(+), 96 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt index f0a66b323..134ad99c4 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt @@ -24,13 +24,13 @@ class LogController : RoutingHandlerFactory { } } - fun getLogs(id: String): List { + private fun getLogs(id: String): List { val logLines = boards() .loadConfig() - ?.getJsonObject(Props.WIDGETS) - ?.getJsonObject(Props.WIDGETS_BY_ID) - ?.getJsonObject(id) - ?.getInteger(Props.LOG_LINES) + .getJsonObject(Props.WIDGETS) + .getJsonObject(Props.WIDGETS_BY_ID) + .getJsonObject(id) + .getInteger(Props.LOG_LINES) ?: LogViewerWidget.DEFAULT_LOG_LINES.toInt() return fetchLogs(id, logLines) @@ -38,12 +38,11 @@ class LogController : RoutingHandlerFactory { private fun fetchLogs(id: String, logLines: Int): List { return LogStorage.database - ?.getCollection(id) - ?.find() - ?.sort(Sorts.descending(Log.SEQ)) - ?.limit(logLines) - ?.mapNotNull { it?.asLog() } - ?.sortedBy { it.seq } - ?: emptyList() + .getCollection(id) + .find() + .sort(Sorts.descending(Log.SEQ)) + .limit(logLines) + .map { it.asLog() } + .sortedBy { it.seq } } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index 96c3ed653..00da57c6d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -43,34 +43,34 @@ class LogStorage( override fun start() { super.start() - logsCollection?.createIndex(Indexes.descending(Log.SEQ)) + logsCollection.createIndex(Indexes.descending(Log.SEQ)) } /** Returns a logs collection associated with this widget. */ - private val logsCollection: MongoCollection? - get() = database?.getCollection(config.id) + private val logsCollection: MongoCollection + get() = database.getCollection(config.id) // Storage configuration /** Returns a logs collection configuration associated with this widget (if present). */ private val collectionState: LogCollectionState? get() = stateCollection - ?.find(eq(Log.ID, config.id)) - ?.first() - ?.let { it.asLogCollectionState() } + .find(eq(Log.ID, config.id)) + .first() + ?.asLogCollectionState() /** Saves or deletes a logs collection [state] associated with this widget. */ private fun saveState(state: LogCollectionState?) { if (state != null) { val options = ReplaceOptions().upsert(true) stateCollection - ?.replaceOne( + .replaceOne( eq(LogCollectionState.ID, config.id), state.toDocument(), options ) } else { - stateCollection?.deleteOne((eq(LogCollectionState.ID, config.id))) + stateCollection.deleteOne((eq(LogCollectionState.ID, config.id))) } } @@ -78,20 +78,20 @@ class LogStorage( /** Deletes all logs from the collection. */ private fun deleteAllLogs() { - logsCollection?.drop() + logsCollection.drop() } /** Deletes the [n] first logs (ordered by their sequence number). */ private fun deleteFirstLogs(n: Long) { - val collection = logsCollection ?: return - val ids = collection + val ids = logsCollection .find() .sort(ascending(Log.SEQ)) .limit(n.toInt()) - .mapNotNull { it?.getObjectId(Log.ID) } + .map { it.getObjectId(Log.ID) } + .toList() if (ids.isNotEmpty()) { try { - val result = collection.deleteMany(`in`(Log.ID, ids)) + val result = logsCollection.deleteMany(`in`(Log.ID, ids)) LOGGER.debug("Deleted ${result.deletedCount} first logs") } catch (exception: MongoException) { LOGGER.error("Cannot delete first logs: $exception") @@ -101,11 +101,10 @@ class LogStorage( /** Deletes old logs (based on the number of days before expiration). */ private fun deleteOldLogs() { - val collection = logsCollection ?: return val now = Instant.now().epochSecond val beforeTimestamp = now - (config.expirationDays * DAY_TO_TIMESTAMP) try { - val result = collection.deleteMany(lt(Log.INSERTED_ON, beforeTimestamp)) + val result = logsCollection.deleteMany(lt(Log.INSERTED_ON, beforeTimestamp)) LOGGER.debug("Deleted ${result.deletedCount} old logs") } catch (exception: MongoException) { LOGGER.error("Cannot delete old logs: $exception") @@ -114,16 +113,13 @@ class LogStorage( /** Deletes oldest logs when logs take up too much space. */ private fun deleteSpaceConsumingLogs() { - val database = database ?: return - val collection = logsCollection ?: return - val size = database .runCommand(Document(STATS_COMMAND, config.id)) - .getInteger(STATS_SIZE) ?: 0 + .getInteger(STATS_SIZE) val maxSize = config.fileSizeMB * MB_TO_BYTES if (size > 0 && size > maxSize) { val deleteFactor = ((size - maxSize).toDouble() / size) - val logCount = collection.countDocuments() + val logCount = logsCollection.countDocuments() val toDelete = (logCount.toDouble() * deleteFactor).toLong() if (toDelete > 0) { LOGGER.debug("Deleting $toDelete logs as the size $size exceeds maximum size of $maxSize") @@ -145,14 +141,12 @@ class LogStorage( /** Deletes logs not matching to the rules from the database. */ fun filterExistingLogs() { - val collection = logsCollection ?: return - val fieldName = Log.VARIABLE_DATA + "." + LogVariableData.HEADER val regexes = enabledRegexes.map { regex(fieldName, it.pattern) } if (regexes.isEmpty()) { return } - collection.deleteMany(or(regexes)) + logsCollection.deleteMany(or(regexes)) } /** Downloads new logs and filters them by quarantine rules. */ @@ -178,7 +172,7 @@ class LogStorage( sequence++ } - logsCollection?.insertMany(logs.map { it.toDocument() }) + logsCollection.insertMany(logs.map { it.toDocument() }) } /** Checks how many logs to download, downloads them and saves them to the database. */ @@ -227,7 +221,7 @@ class LogStorage( } val response = prepareResponse(insertedLogs) - vertx?.eventBus()?.send(config.eventBusAddress, response) + vertx.eventBus().send(config.eventBusAddress, response) } /** Deletes all data associated with the widget. */ @@ -248,39 +242,28 @@ class LogStorage( private val MONGO_PASSWORD = System.getenv("MONGO_PASSWORD") ?: "root" private val MONGO_HOST = System.getenv("MONGO_HOST") ?: "mongo" private val MONGO_PORT = System.getenv("MONGO_PORT")?.toIntOrNull() ?: 27017 - private var mongoClient: MongoClient? = null /** Returns a shared instance of the Mongo client. */ - private val client: MongoClient? - get() { - if (mongoClient != null) { - return mongoClient - } - try { - val uri = URI( - MONGO_SCHEME, - "$MONGO_USERNAME:$MONGO_PASSWORD", - MONGO_HOST, - MONGO_PORT, - null, - null, - null - ) - mongoClient = MongoClients.create(uri.toString()) - return mongoClient - } catch (exception: MongoException) { - LOGGER.error("Cannot create a mongo client: $exception") - } - return null + private val mongoClient: MongoClient by lazy { + val uri = URI( + MONGO_SCHEME, + "$MONGO_USERNAME:$MONGO_PASSWORD", + MONGO_HOST, + MONGO_PORT, + null, + null, + null + ) + MongoClients.create(uri.toString()) } /** Returns a database for storing logs and collection states. */ - val database: MongoDatabase? - get() = client?.getDatabase(DATABASE_NAME) + val database: MongoDatabase + get() = mongoClient.getDatabase(DATABASE_NAME) /** Returns a state collection. */ - val stateCollection: MongoCollection? - get() = database?.getCollection(STATE_COLLECTION_NAME) + val stateCollection: MongoCollection + get() = database.getCollection(STATE_COLLECTION_NAME) private val LOGGER: Logger = LoggerFactory.getLogger(LogStorage::class.java) } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt index f6c30b4d8..60433a7cf 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/Log.kt @@ -45,18 +45,11 @@ data class Log( } } -fun Document.asLog(): Log? { - return try { - val id = getObjectId(Log.ID)!! - val seq = getLong(Log.SEQ)!! - val insertedOn = getLong(Log.INSERTED_ON)!! - val date = getLong(Log.DATE)!! - val type = getString(Log.TYPE)!! - - val variableData = getList(Log.VARIABLE_DATA, Document::class.java) - ?.mapNotNull { it?.asLogVariableData() } ?: listOf() - Log(id, seq, insertedOn, date, type, variableData) - } catch (_: NullPointerException) { - null - } -} +fun Document.asLog() = Log( + getObjectId(Log.ID), + getLong(Log.SEQ), + getLong(Log.INSERTED_ON), + getLong(Log.DATE), + getString(Log.TYPE), + getList(Log.VARIABLE_DATA, Document::class.java).map { it.asLogVariableData() } +) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt index 22c68b798..38905824d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogCollectionState.kt @@ -22,14 +22,8 @@ data class LogCollectionState( } } -fun Document.asLogCollectionState(): LogCollectionState? { - return try { - val id = getString(LogCollectionState.ID) - val lastLine = getLong(LogCollectionState.LAST_LINE) - val seq = getLong(LogCollectionState.SEQ) - - LogCollectionState(id, lastLine, seq) - } catch (_: NullPointerException) { - null - } -} +fun Document.asLogCollectionState() = LogCollectionState( + getString(LogCollectionState.ID), + getLong(LogCollectionState.LAST_LINE), + getLong(LogCollectionState.SEQ) +) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt index 0f9f0c2ce..33efe6873 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/LogVariableData.kt @@ -21,9 +21,7 @@ data class LogVariableData( } } -fun Document.asLogVariableData(): LogVariableData? { - val header = getString(LogVariableData.HEADER) ?: return null - val description = getString(LogVariableData.DESCRIPTION) ?: return null - - return LogVariableData(header, description) -} +fun Document.asLogVariableData() = LogVariableData( + getString(LogVariableData.HEADER), + getString(LogVariableData.DESCRIPTION) +) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt index 70fbfda8e..2023bb068 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt @@ -31,7 +31,6 @@ data class QuarantineRule( fun from(array: JsonArray): List = array - .mapNotNull { it } .mapNotNull { it as? JsonObject } .map { from(it) } } From 2733af6a022e533e14d2cb3faeff3c6c8249e25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Wed, 5 Jan 2022 00:57:23 +0100 Subject: [PATCH 171/226] Changed return value in SSHClient #445 --- .../src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt index 38c0bd03d..47f16d724 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/SSHClient.kt @@ -51,7 +51,7 @@ class SSHClient(private val config: JsonObject) { return try { readResponse(inputStream) } catch (e: IOException) { - null + e.message } finally { channel.disconnect() } From 0904b48484f707fabfba55a894824a952161e7af Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Wed, 5 Jan 2022 14:18:51 +0100 Subject: [PATCH 172/226] Add export to file button and tests bugfixes --- .../logStorage/model/QuarantineRuleTest.kt | 10 +++---- .../types/LogViewerWidget/LogList/index.js | 26 +++---------------- .../Toolbar/QuarantineModal/index.js | 8 +++--- .../types/LogViewerWidget/Toolbar/helpers.js | 18 +++++++++++++ .../types/LogViewerWidget/Toolbar/index.js | 10 ++++++- .../widgets/types/LogViewerWidget/index.js | 20 +++++++++++++- .../cypress/integration/logsViewer.js | 9 +++---- 7 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/helpers.js diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt index a5c1a8ccd..d7b130b4d 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt @@ -17,12 +17,12 @@ class QuarantineRuleTest { } @Test - fun `Ignores different fields`() { + fun `Uses default rule if at least one of fields are missing`() { val rule = QuarantineRule.from(JsonObject("{\"Field1\": \"Example field\", \"SecondField\": 2, \"Another field\": \"Lorem ipsum\", \"Last field\": []}")) - assertNull(rule?.label) - assertNull(rule?.reasonField) - assertNull(rule?.regex) - assertNull(rule?.enabled) + assertEquals("Default", rule?.label) + assertEquals("", rule?.reasonField) + assertEquals("(?!x)x", rule?.regex?.pattern) + assertEquals(false, rule?.enabled) } @Test diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 0eb2cf0c8..5b197543b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -1,13 +1,5 @@ import React, { useRef, useEffect, useState } from 'react'; -import { - getGridTemplate, - filterByRegExp, - filterByDateSpan, - isLogHighlighted, - filterByLevel -} from './helpers'; -import { getFilters, getLevel } from '../Toolbar/FilterPicker/helpers'; -import { getDateSpan } from '../Toolbar/DateRangePicker/helpers'; +import { getGridTemplate, isLogHighlighted } from './helpers'; import { useTheme } from '@material-ui/core'; import LogEntry from './LogEntry'; import { @@ -21,7 +13,6 @@ import { } from './styled'; export default function LogList({ - widgetLocalStorage, logs, template, search, @@ -32,15 +23,6 @@ export default function LogList({ const scrollerRef = useRef(null); const [scroll, setScroll] = useState(0); - const filters = getFilters(widgetLocalStorage); - const level = getLevel(widgetLocalStorage); - const dateSpan = getDateSpan(widgetLocalStorage); - - const filteredLogs = logs - ?.filter(log => filterByLevel(log, level)) - .filter(log => filterByDateSpan(log, dateSpan)) - .filter(log => filterByRegExp(log, filters)); - console.log(template); const VariableLogListHeader = () => ( @@ -59,7 +41,7 @@ export default function LogList({ }); } setScroll(scrollerRef.current.scrollTop); - }, [filteredLogs, shouldFollowLogs, scroll]); + }, [logs, shouldFollowLogs, scroll]); const stopFollowingOnUpScroll = () => { if (scroll > scrollerRef.current.scrollTop) { @@ -72,7 +54,7 @@ export default function LogList({ isScrolling && stopFollowingOnUpScroll(); const getLogByIndex = index => { - const log = filteredLogs[index]; + const log = logs[index]; return ( (scrollerRef.current = ref)} isScrolling={handleScrollChange} - totalCount={filteredLogs.length} + totalCount={logs.length} increaseViewportBy={300} // defines loading overlap (in pixels) itemContent={getLogByIndex} /> diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index 666e59a03..4bb1b3a6c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -6,7 +6,8 @@ import { ListItem, ListItemText, ListItemSecondaryAction, - Switch + Switch, + Tooltip } from '@material-ui/core'; import { StyledButton } from './styled'; import { getIsAuthenticated } from '../../../../../../selectors'; @@ -85,8 +86,9 @@ const QuarantineModal = ({ wid, quarantine }) => { ) => items.map(({ id, label, checked, reasonField }) => ( - - + + + { + const a = document.createElement('a'); + const today = new Date(); + const options = { + weekday: 'long', + year: 'numeric', + month: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }; + a.download = today.toLocaleDateString('en-US', options) + '.txt'; + a.href = URL.createObjectURL(blob); + a.addEventListener('click', e => { + setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000); + }); + a.click(); +}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 38f09e0a5..03369abfd 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -9,7 +9,9 @@ import ToolbarGroup from './ToolbarGroup'; import DateRangePicker from './DateRangePicker'; import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; import DeleteIcon from '@material-ui/icons/Delete'; +import GetAppIcon from '@material-ui/icons/GetApp'; import FilterPicker from './FilterPicker'; +import { saveFile } from './helpers'; const Toolbar = ({ wid, @@ -18,7 +20,8 @@ const Toolbar = ({ setSearchFilter, shouldFollowLogs, handleFollowChange, - lastLog + lastLog, + logs }) => { const handleClearLogs = () => { const date = lastLog?.date; @@ -59,6 +62,11 @@ const Toolbar = ({ onClick={handleClearLogs} Icon={DeleteIcon} /> + saveFile(new Blob([JSON.stringify(logs)]))} + /> ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 62a8ff6ba..1ea697605 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -9,6 +9,13 @@ import { Container } from './styled'; import { getInitialLogs } from '../../../../utils/fetch'; import { joinLogs } from './helpers'; import { SimilarLogsContext } from './context'; +import { getFilters, getLevel } from './Toolbar/FilterPicker/helpers'; +import { getDateSpan } from './Toolbar/DateRangePicker/helpers'; +import { + filterByRegExp, + filterByDateSpan, + filterByLevel +} from './LogList/helpers'; const LogViewerWidget = ({ id }) => { const widgetData = useSelector( @@ -47,6 +54,15 @@ const LogViewerWidget = ({ id }) => { const [filterSimilarLogs, setFilterSimilarLogs] = useState(null); const [quarantineSimilarLogs, setQuarantineSimilarLogs] = useState(null); + const filters = getFilters(widgetLocalStorage); + const level = getLevel(widgetLocalStorage); + const dateSpan = getDateSpan(widgetLocalStorage); + + const filteredLogs = storedLogs + ?.filter(log => filterByLevel(log, level)) + .filter(log => filterByDateSpan(log, dateSpan)) + .filter(log => filterByRegExp(log, filters)); + return ( { storedLogs.length > 0 && storedLogs[storedLogs.length - 1] } + logs={filteredLogs} /> {storedLogs && ( { '' ); cy.get('[data-cy="log-entry"]').should('exist'); - }); }); - - describe('Quarantine', () => { + }); + + describe('Quarantine', () => { it('should allow logged in users to click quarantine button', () => { cy.get('[data-cy="advanced-filters-button"]').click(); cy.get('[data-cy="quarantine-show-dialog-button"]').should('exist'); cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); }); - + it('should not allow logged out users to click quarantine button', () => { cy.get('[data-cy="user-login-logout-icon"]').click(); cy.get('[data-cy="advanced-filters-button"]').click(); cy.get('[data-cy="quarantine-show-dialog-button"]').should('not.exist'); cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); cy.login(); - widget.remove(); }); }); From cc774efb817e9cc2773920558f8bb45d78df461a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 5 Jan 2022 22:34:57 +0100 Subject: [PATCH 173/226] Add initial quarantine end date implementation --- .../cogboard/logStorage/LogStorage.kt | 9 ++- .../logStorage/model/QuarantineRule.kt | 10 ++- .../logStorage/model/QuarantineRuleTest.kt | 30 +++++---- .../widgets/dialogFields/TimestampInput.js | 65 +++++++++++++++++++ .../components/widgets/dialogFields/index.js | 8 +++ .../components/widgets/dialogFields/styled.js | 25 ++++++- .../Toolbar/QuarantineModal/QuarantineForm.js | 7 +- 7 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index 00da57c6d..6fc99c2f0 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -39,7 +39,14 @@ class LogStorage( /** Returns the list of regexes of enabled rules. */ private val enabledRegexes: List - get() = rules.filter { it.enabled }.map { it.regex } + get() { + val now = Instant.now() + return rules + .filter { rule -> + rule.enabled && (rule.endTimestamp?.let { it > now } ?: true) + } + .map { it.regex } + } override fun start() { super.start() diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt index 2023bb068..dbc3b3524 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRule.kt @@ -2,19 +2,22 @@ package com.cognifide.cogboard.logStorage.model import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject +import java.time.Instant data class QuarantineRule( val label: String, val reasonField: String, val regex: Regex, - val enabled: Boolean + val enabled: Boolean, + val endTimestamp: Instant? ) { companion object { private const val LABEL = "label" private const val REASON = "reasonField" private const val REGEX = "regExp" private const val ENABLED = "checked" - private val default = QuarantineRule("Default", "", "(?!x)x".toRegex(), false) + private const val END_TIMESTAMP = "endTimestamp" + private val default = QuarantineRule("Default", "", "(?!x)x".toRegex(), false, null) fun from(json: JsonObject): QuarantineRule { return try { @@ -22,7 +25,8 @@ data class QuarantineRule( json.getString(LABEL)!!, json.getString(REASON)!!, json.getString(REGEX)!!.toRegex(), - json.getBoolean(ENABLED)!! + json.getBoolean(ENABLED)!!, + json.getLong(END_TIMESTAMP)?.let { Instant.ofEpochSecond(it) } ) } catch (_: NullPointerException) { default diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt index a5c1a8ccd..fb203a785 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt @@ -4,25 +4,26 @@ import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull class QuarantineRuleTest { @Test fun `Properly parses correct rule`() { - val rule = QuarantineRule.from(JsonObject("{\"label\": \"Example label\", \"reasonField\": \"Reason\", \"regExp\": \"^a\", \"checked\": true}")) - assertEquals("Example label", rule?.label) - assertEquals("Reason", rule?.reasonField) - assertEquals("^a", rule?.regex?.pattern) - assertEquals(true, rule?.enabled) + val rule = QuarantineRule.from(JsonObject("""{"label": "Example label", "reasonField": "Reason", "regExp": "^a", "checked": true}""")) + assertEquals("Example label", rule.label) + assertEquals("Reason", rule.reasonField) + assertEquals("^a", rule.regex.pattern) + assertEquals(true, rule.enabled) + assertEquals(null, rule.endTimestamp) } @Test - fun `Ignores different fields`() { - val rule = QuarantineRule.from(JsonObject("{\"Field1\": \"Example field\", \"SecondField\": 2, \"Another field\": \"Lorem ipsum\", \"Last field\": []}")) - assertNull(rule?.label) - assertNull(rule?.reasonField) - assertNull(rule?.regex) - assertNull(rule?.enabled) + fun `Returns default rule when incorrect`() { + val rule = QuarantineRule.from(JsonObject("""{"Field1": "Example field", "SecondField": 2, "Another field": "Lorem ipsum", "Last field": []}""")) + assertEquals("Default", rule.label) + assertEquals("", rule.reasonField) + assertEquals("(?!x)x", rule.regex.pattern) + assertEquals(false, rule.enabled) + assertEquals(null, rule.endTimestamp) } @Test @@ -32,11 +33,12 @@ class QuarantineRuleTest { } @Test - fun `Correctly parser rules in array`() { - val rules = QuarantineRule.from(JsonArray("[{\"label\": \"Example label\", \"reasonField\": \"Reason\", \"regExp\": \"^a\", \"checked\": true}]")) + fun `Correctly parses rules in array`() { + val rules = QuarantineRule.from(JsonArray("""[{"label": "Example label", "reasonField": "Reason", "regExp": "^a", "checked": true}]""")) assertEquals("Example label", rules[0].label) assertEquals("Reason", rules[0].reasonField) assertEquals("^a", rules[0].regex.pattern) assertEquals(true, rules[0].enabled) + assertEquals(null, rules[0].endTimestamp) } } \ No newline at end of file diff --git a/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js new file mode 100644 index 000000000..ad810edfb --- /dev/null +++ b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js @@ -0,0 +1,65 @@ +import React from 'react'; + +import { StyledValidationMessages } from '../../WidgetForm/styled'; +import { hasError } from '../../../utils/components'; +import MomentUtils from '@date-io/moment'; +import moment from 'moment-timezone'; +import { MuiPickersUtilsProvider } from '@material-ui/pickers'; +import { prepareChangeEvent } from './helpers'; +import CloseIcon from '@material-ui/icons/Close'; +import { + DatePickerWrapper, + StyledDateTimePicker, + StyledIconButton +} from './styled'; + +const TimestampInput = ({ + error, + dataCy, + value, + label, + onChange, + ...other +}) => { + const handleChange = date => + onChange(prepareChangeEvent(date && date.utc().unix(), 'number?')); + + return ( + + + + } + /> + + {Number.isInteger(value) && ( + handleChange(null)}> + + + )} + + ); +}; + +export default TimestampInput; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/index.js b/cogboard-webapp/src/components/widgets/dialogFields/index.js index e1bc4fa84..99d07a3ea 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/index.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/index.js @@ -39,6 +39,7 @@ import ToDoListInput from './ToDoListinput'; import WidgetTypeField from './WidgetTypeField'; import FileTextInput from './FileTextInput'; import ParserTypeInput from './ParserTypeInput'; +import TimestampInput from './TimestampInput'; const dialogFields = { LabelField: { @@ -645,6 +646,13 @@ const dialogFields = { label: 'Reason', validator: () => string() }, + EndTimestampField: { + component: TimestampInput, + name: 'endTimestamp', + label: 'End date (optional)', + initialValue: null, + validator: () => number().nullable() + }, LogParserField: { component: ParserTypeInput, name: 'logParserField', diff --git a/cogboard-webapp/src/components/widgets/dialogFields/styled.js b/cogboard-webapp/src/components/widgets/dialogFields/styled.js index 5cbb6fdcc..c649a0a7e 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/styled.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/styled.js @@ -2,7 +2,16 @@ import styled from '@emotion/styled/macro'; import NumberInput from './NumberInput'; import IntegerInput from './IntegerInput'; import { COLORS } from '../../../constants'; -import { Box, Input, Fab, List, FormControl, Button } from '@material-ui/core'; +import { + Box, + Input, + Fab, + List, + FormControl, + Button, + IconButton +} from '@material-ui/core'; +import { DateTimePicker } from '@material-ui/pickers'; export const StyledNumberInput = styled(NumberInput)` flex-basis: calc(50% - 18px); @@ -185,3 +194,17 @@ export const DeleteButton = styled(Button)` background-color: ${COLORS.DARK_RED}; } `; + +export const DatePickerWrapper = styled.div` + position: relative; +`; + +export const StyledIconButton = styled(IconButton)` + position: absolute; + right: 0; + top: 24px; +`; + +export const StyledDateTimePicker = styled(DateTimePicker)` + width: 100%; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js index 367636ce8..74e4979f0 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js @@ -25,7 +25,12 @@ const QuarantineForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [similarLogs.quarantine]); - const formFields = ['LabelField', 'RegExpField', 'ReasonField']; + const formFields = [ + 'LabelField', + 'RegExpField', + 'ReasonField', + 'EndTimestampField' + ]; const constraints = { LabelField: { max: 25, From d2c8b11bf4a298b37fa62227d546fea4c059e842 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 6 Jan 2022 15:03:18 +0100 Subject: [PATCH 174/226] Rename MockLogParser to DefaultLogParser #467 --- .../cogboard/widget/type/logviewer/LogViewerWidget.kt | 2 +- .../{MockLogParserStrategy.kt => DefaultLogParserStrategy.kt} | 4 ++-- .../type/logviewer/logparser/LogParserStrategyFactory.kt | 4 ++-- ...gParserStrategyTest.kt => DefaultLogParserStrategyTest.kt} | 4 ++-- .../src/components/widgets/dialogFields/ParserTypeInput.js | 2 +- functional/cypress-tests/cypress/fixtures/Widgets.js | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/{MockLogParserStrategy.kt => DefaultLogParserStrategy.kt} (93%) rename cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/{MockLogParserStrategyTest.kt => DefaultLogParserStrategyTest.kt} (88%) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt index 9eb08b25b..ceec5f6cd 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogViewerWidget.kt @@ -114,7 +114,7 @@ class LogViewerWidget( private fun determineLogParsingStrategy() = LogParserStrategyFactory() .build(config.getString(Props.LOG_PARSER) - ?: LogParserStrategyFactory.Type.MOCK.toString() + ?: LogParserStrategyFactory.Type.DEFAULT.toString() ) companion object { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/DefaultLogParserStrategy.kt similarity index 93% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/DefaultLogParserStrategy.kt index b8235b181..7ba364989 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategy.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/DefaultLogParserStrategy.kt @@ -6,7 +6,7 @@ import java.time.LocalDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -class MockLogParserStrategy : LogParserStrategy { +class DefaultLogParserStrategy : LogParserStrategy { override val variableFields = listOf("Provider", "Message") private val regex = """^(?<$DATE>[0-9-:]+) \*(?<$TYPE>[A-Z]+)\* \[(?<$PROVIDER>[a-zA-Z]+)\][ ]+(?<$MESSAGE>.+)$""" @@ -45,7 +45,7 @@ class MockLogParserStrategy : LogParserStrategy { date = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC), type = "ERROR", variableData = listOf( - LogVariableData("MockLogParserStrategy", "No description"), + LogVariableData("DefaultLogParserStrategy", "No description"), LogVariableData("Cannot parse a log", "Line causing the error: $line") ) ) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt index 6f2b2b744..7520f74e6 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/LogParserStrategyFactory.kt @@ -2,13 +2,13 @@ package com.cognifide.cogboard.widget.type.logviewer.logparser class LogParserStrategyFactory { enum class Type { - MOCK + DEFAULT } fun build(typeStr: String): LogParserStrategy { return try { when (Type.valueOf(typeStr.toUpperCase())) { - Type.MOCK -> MockLogParserStrategy() + Type.DEFAULT -> DefaultLogParserStrategy() } } catch (e: IllegalArgumentException) { throw UnknownParserTypeException("Unknown log parsing type") diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/DefaultLogParserStrategyTest.kt similarity index 88% rename from cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt rename to cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/DefaultLogParserStrategyTest.kt index 2fd8039c3..3cb1fca1c 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/MockLogParserStrategyTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/logviewer/logparser/DefaultLogParserStrategyTest.kt @@ -3,9 +3,9 @@ package com.cognifide.cogboard.widget.type.logviewer.logparser import org.junit.jupiter.api.Test import java.lang.AssertionError -class MockLogParserStrategyTest { +class DefaultLogParserStrategyTest { private val sampleLog = "2021-11-06:22:40:25 *DEBUG* [FelixStartLevel] Integer lobortis. bibendum Nulla mi" - private val parser = MockLogParserStrategy() + private val parser = DefaultLogParserStrategy() @Test fun parseSampleLog() { diff --git a/cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js b/cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js index bf98baff5..6e625c46e 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/ParserTypeInput.js @@ -3,7 +3,7 @@ import DropdownField from '../../DropdownField'; import { MenuItem } from '@material-ui/core'; const ParserTypeInput = props => { - const parsers = [{ id: 'mock', label: 'Mock Parser' }]; + const parsers = [{ id: 'default', label: 'Default' }]; return ( diff --git a/functional/cypress-tests/cypress/fixtures/Widgets.js b/functional/cypress-tests/cypress/fixtures/Widgets.js index 73ed399db..561b1d1a8 100644 --- a/functional/cypress-tests/cypress/fixtures/Widgets.js +++ b/functional/cypress-tests/cypress/fixtures/Widgets.js @@ -143,7 +143,7 @@ module.exports = { endpoint: 'endpoint6', schedulePeriod: '60', path: '/home/mock/example.txt', - parserType: 'mock' + parserType: 'default' }, serviceCheck: { name: 'Service Check', From 2211d230c4b5ab81935ad8e7830429aff67a2a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Thu, 6 Jan 2022 18:47:29 +0100 Subject: [PATCH 175/226] Change the enabling/disabling of quarantine rules --- .../Toolbar/QuarantineModal/index.js | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index 666e59a03..7a94edfaa 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -18,6 +18,7 @@ import QuarantineForm from './QuarantineForm'; import EditQFilter from './EditQFilter'; import DeleteItem from '../../../../../DeleteItem'; import { SimilarLogsContext } from '../../context'; +import moment from 'moment-timezone'; const QuarantineModal = ({ wid, quarantine }) => { const isAuthenticated = useSelector(getIsAuthenticated); @@ -32,6 +33,25 @@ const QuarantineModal = ({ wid, quarantine }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [similarLogs.quarantine]); + const isChecked = (checked, endTimestamp) => { + if (endTimestamp) { + const inFuture = moment.utc(endTimestamp * 1000).local() > moment(); + return checked && inFuture; + } + return checked; + }; + + const toggleChecked = rule => { + const endTimestamp = rule.endTimestamp; + if (endTimestamp) { + const inFuture = moment.utc(endTimestamp * 1000).local() > moment(); + if (!inFuture) { + return { ...rule, checked: true, endTimestamp: null }; + } + } + return { ...rule, checked: !rule.checked }; + }; + const handleQuarantineClick = event => { event.stopPropagation(); openDialog(); @@ -59,8 +79,8 @@ const QuarantineModal = ({ wid, quarantine }) => { const handleSwitchChange = id => { postWidgetContentUpdate({ id: wid, - quarantineRules: quarantine.map(filter => - filter.id === id ? { ...filter, checked: !filter.checked } : filter + quarantineRules: quarantine.map(rule => + rule.id === id ? toggleChecked(rule) : rule ) }); }; @@ -83,7 +103,7 @@ const QuarantineModal = ({ wid, quarantine }) => { editAction, deleteAction ) => - items.map(({ id, label, checked, reasonField }) => ( + items.map(({ id, label, checked, reasonField, endTimestamp }) => ( @@ -96,7 +116,7 @@ const QuarantineModal = ({ wid, quarantine }) => { deleteAction={deleteAction} /> handleSwitchChange(id)} color="secondary" > From a913d4327a46088c3f3cb21bb92c0dffb5708db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Thu, 6 Jan 2022 18:58:31 +0100 Subject: [PATCH 176/226] Modify the quarantine rules tests --- .../logStorage/model/QuarantineRuleTest.kt | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt index fb203a785..d2cdfb007 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/logStorage/model/QuarantineRuleTest.kt @@ -8,7 +8,32 @@ import org.junit.jupiter.api.Assertions.assertEquals class QuarantineRuleTest { @Test fun `Properly parses correct rule`() { - val rule = QuarantineRule.from(JsonObject("""{"label": "Example label", "reasonField": "Reason", "regExp": "^a", "checked": true}""")) + val rule = QuarantineRule.from(JsonObject(""" + { + "label": "Example label", + "reasonField": "Reason", + "regExp": "^a", + "checked": true, + "endTimestamp": 1641491404 + } + """)) + assertEquals("Example label", rule.label) + assertEquals("Reason", rule.reasonField) + assertEquals("^a", rule.regex.pattern) + assertEquals(true, rule.enabled) + assertEquals(1641491404, rule.endTimestamp?.epochSecond) + } + + @Test + fun `Properly parses a rule without end timestamp`() { + val rule = QuarantineRule.from(JsonObject(""" + { + "label": "Example label", + "reasonField": "Reason", + "regExp": "^a", + "checked": true + } + """)) assertEquals("Example label", rule.label) assertEquals("Reason", rule.reasonField) assertEquals("^a", rule.regex.pattern) @@ -18,7 +43,14 @@ class QuarantineRuleTest { @Test fun `Returns default rule when incorrect`() { - val rule = QuarantineRule.from(JsonObject("""{"Field1": "Example field", "SecondField": 2, "Another field": "Lorem ipsum", "Last field": []}""")) + val rule = QuarantineRule.from(JsonObject(""" + { + "Field1": "Example field", + "SecondField": 2, + "Another field": "Lorem ipsum", + "Last field": [] + } + """)) assertEquals("Default", rule.label) assertEquals("", rule.reasonField) assertEquals("(?!x)x", rule.regex.pattern) @@ -29,16 +61,38 @@ class QuarantineRuleTest { @Test fun `Empty JSON array creates empty rules array`() { val rules = QuarantineRule.from(JsonArray("[]")) - assertEquals(rules.size, 0) + assertEquals(0, rules.size) } @Test fun `Correctly parses rules in array`() { - val rules = QuarantineRule.from(JsonArray("""[{"label": "Example label", "reasonField": "Reason", "regExp": "^a", "checked": true}]""")) + val rules = QuarantineRule.from(JsonArray(""" + [ + { + "label":"Example label", + "reasonField":"Reason", + "regExp":"^a", + "checked":true, + "endTimestamp": 1641491404 + }, + { + "label":"Example label", + "reasonField":"Reason", + "regExp":"^a", + "checked":true + } + ] + """)) + assertEquals(2, rules.size) assertEquals("Example label", rules[0].label) assertEquals("Reason", rules[0].reasonField) assertEquals("^a", rules[0].regex.pattern) assertEquals(true, rules[0].enabled) - assertEquals(null, rules[0].endTimestamp) + assertEquals(1641491404, rules[0].endTimestamp?.epochSecond) + assertEquals("Example label", rules[1].label) + assertEquals("Reason", rules[1].reasonField) + assertEquals("^a", rules[1].regex.pattern) + assertEquals(true, rules[1].enabled) + assertEquals(null, rules[1].endTimestamp) } } \ No newline at end of file From f986c74677d03d17c67e6d403b1769977ce018d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Fri, 7 Jan 2022 18:18:44 +0100 Subject: [PATCH 177/226] Changed compose files --- .gitignore | 1 + cogboard-local-compose.yml | 17 ++++++++------- docker/Dockerfile | 4 ++-- gradle/docker.gradle.kts | 8 ++++++- gradle/prepareCogboardCompose.gradle.kts | 1 + gradle/prepareMongoConfig.gradle.kts | 27 ++++++++++++++++++++++++ 6 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 gradle/prepareMongoConfig.gradle.kts diff --git a/.gitignore b/.gitignore index 26d5544f2..863c97733 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ Thumbs.db # Project specific /mnt/*.json /mnt/*.backup +/mnt/init-mongo.js /mnt/content/*.json /mnt/.version /cogboard-compose.yml diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index f38a8398d..d7414c3a4 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -26,7 +26,7 @@ services: image: "cogboard/cogboard-app:${COGBOARD_VERSION}" environment: - COGBOARD_VERSION=${COGBOARD_VERSION} - - MONGO_USERNAME=${MONGO_USERNAME} + - MONGO_USERNAME=${MONGO_USER} - MONGO_PASSWORD=${MONGO_PASSWORD} - MONGO_HOST=mongo - MONGO_PORT=27017 @@ -34,18 +34,19 @@ services: - "./mnt:/data" networks: - cognet - # ports: - # - "18092:18092" + ports: + - "18092:18092" mongo: image: mongo:4 restart: always environment: - MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} - MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} + MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD} MONGO_INITDB_DATABASE: "logs" volumes: - "./mnt/mongo:/data/db" + - "./mnt/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro" networks: - cognet @@ -55,9 +56,9 @@ services: ports: - 8099:8081 environment: - ME_CONFIG_MONGODB_ADMINUSERNAME: "root" - ME_CONFIG_MONGODB_ADMINPASSWORD: "root" - ME_CONFIG_MONGODB_URL: "mongodb://root:root@mongo:27017/" + ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_USER} + ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD} + ME_CONFIG_MONGODB_URL: "mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/" networks: - cognet diff --git a/docker/Dockerfile b/docker/Dockerfile index 4f25b5a14..8df979145 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ LABEL maintainer="CogBoard Project" COPY ./out/knotx /usr/local/knotx -# RUN sed -i 's/# JVM_DEBUG=/JVM_DEBUG=/g' /usr/local/knotx/bin/knotx -# RUN sed -i 's/suspend=n/suspend=y/g' /usr/local/knotx/bin/knotx +RUN sed -i 's/# JVM_DEBUG=/JVM_DEBUG=/g' /usr/local/knotx/bin/knotx +RUN sed -i 's/suspend=n/suspend=y/g' /usr/local/knotx/bin/knotx CMD [ "knotx", "run-knotx" ] \ No newline at end of file diff --git a/gradle/docker.gradle.kts b/gradle/docker.gradle.kts index 33f38cb1a..e67dadc89 100644 --- a/gradle/docker.gradle.kts +++ b/gradle/docker.gradle.kts @@ -29,6 +29,8 @@ val cypressConfigPath = "cypress/config/$cypressEnvCode.json" val network = "${project.name}-local_cognet" val wsPort = project.property("ws.port") val appPort = project.property("app.port") +val mongoUsername = project.property("mongo.user") +val mongoPassword = project.property("mongo.password") logger.lifecycle(">> dockerContainerName: $dockerContainerName") logger.lifecycle(">> dockerImageName: $dockerImageName") @@ -90,9 +92,11 @@ tasks { register("deployLocal") { environment.put("COGBOARD_VERSION", version) + environment.put("MONGO_USER", mongoUsername) + environment.put("MONGO_PASSWORD", mongoPassword) group = "swarm" commandLine = listOf("docker", "stack", "deploy", "-c", "${project.name}-local-compose.yml", "${project.name}-local") - dependsOn("initSwarm", "buildImage", "awaitLocalStackUndeployed") + dependsOn("initSwarm", "buildImage", "awaitLocalStackUndeployed", "prepareMongoConfig") mustRunAfter("undeployLocal") } @@ -160,4 +164,6 @@ tasks { register("prepareDocker") { dependsOn("cleanDistribution", "overwriteCustomFiles", "copyDockerfile", "copyWsConf") } + + apply(from = "gradle/prepareMongoConfig.gradle.kts") } diff --git a/gradle/prepareCogboardCompose.gradle.kts b/gradle/prepareCogboardCompose.gradle.kts index 0e12fd6cd..aea66044f 100644 --- a/gradle/prepareCogboardCompose.gradle.kts +++ b/gradle/prepareCogboardCompose.gradle.kts @@ -32,6 +32,7 @@ services: MONGO_INITDB_DATABASE: "logs" volumes: - "./mnt/mongo:/data/db" + - "./mnt/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro" frontend: image: "cogboard/cogboard-web:$currentVersion" diff --git a/gradle/prepareMongoConfig.gradle.kts b/gradle/prepareMongoConfig.gradle.kts new file mode 100644 index 000000000..37218f945 --- /dev/null +++ b/gradle/prepareMongoConfig.gradle.kts @@ -0,0 +1,27 @@ +tasks { + register("prepareMongoConfig") { + createMongoConfigFile() + } +} + +fun createMongoConfigFile() { + val configFilePath = "$rootDir/mnt/init-mongo.js" + logger.lifecycle(">> createZip >> Creating $configFilePath") + + val user = project.property("mongo.user") + val password = project.property("mongo.password") + File(configFilePath).writeText(""" + db.createUser( + { + user : "$user", + pwd : "$password", + roles : [ + { + role: "root", + db : "logs" + } + ] + } + ) + """.trimIndent()) +} \ No newline at end of file From 9626633799ff360baf4699a5ff219ab1d9521c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sat, 8 Jan 2022 01:06:40 +0100 Subject: [PATCH 178/226] Deleted docker config script creation --- cogboard-local-compose.yml | 1 - gradle/docker.gradle.kts | 4 +--- gradle/prepareCogboardCompose.gradle.kts | 1 - gradle/prepareMongoConfig.gradle.kts | 27 ------------------------ 4 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 gradle/prepareMongoConfig.gradle.kts diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index d7414c3a4..02a7210bf 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -46,7 +46,6 @@ services: MONGO_INITDB_DATABASE: "logs" volumes: - "./mnt/mongo:/data/db" - - "./mnt/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro" networks: - cognet diff --git a/gradle/docker.gradle.kts b/gradle/docker.gradle.kts index e67dadc89..889b314a5 100644 --- a/gradle/docker.gradle.kts +++ b/gradle/docker.gradle.kts @@ -96,7 +96,7 @@ tasks { environment.put("MONGO_PASSWORD", mongoPassword) group = "swarm" commandLine = listOf("docker", "stack", "deploy", "-c", "${project.name}-local-compose.yml", "${project.name}-local") - dependsOn("initSwarm", "buildImage", "awaitLocalStackUndeployed", "prepareMongoConfig") + dependsOn("initSwarm", "buildImage", "awaitLocalStackUndeployed") mustRunAfter("undeployLocal") } @@ -164,6 +164,4 @@ tasks { register("prepareDocker") { dependsOn("cleanDistribution", "overwriteCustomFiles", "copyDockerfile", "copyWsConf") } - - apply(from = "gradle/prepareMongoConfig.gradle.kts") } diff --git a/gradle/prepareCogboardCompose.gradle.kts b/gradle/prepareCogboardCompose.gradle.kts index aea66044f..0e12fd6cd 100644 --- a/gradle/prepareCogboardCompose.gradle.kts +++ b/gradle/prepareCogboardCompose.gradle.kts @@ -32,7 +32,6 @@ services: MONGO_INITDB_DATABASE: "logs" volumes: - "./mnt/mongo:/data/db" - - "./mnt/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro" frontend: image: "cogboard/cogboard-web:$currentVersion" diff --git a/gradle/prepareMongoConfig.gradle.kts b/gradle/prepareMongoConfig.gradle.kts deleted file mode 100644 index 37218f945..000000000 --- a/gradle/prepareMongoConfig.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -tasks { - register("prepareMongoConfig") { - createMongoConfigFile() - } -} - -fun createMongoConfigFile() { - val configFilePath = "$rootDir/mnt/init-mongo.js" - logger.lifecycle(">> createZip >> Creating $configFilePath") - - val user = project.property("mongo.user") - val password = project.property("mongo.password") - File(configFilePath).writeText(""" - db.createUser( - { - user : "$user", - pwd : "$password", - roles : [ - { - role: "root", - db : "logs" - } - ] - } - ) - """.trimIndent()) -} \ No newline at end of file From 04ce2eeb50cd36290bb1f363ececbbe3a158958b Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 11:14:58 +0100 Subject: [PATCH 179/226] Enlarge dialog widths #467 --- cogboard-webapp/src/components/AppDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/AppDialog.js b/cogboard-webapp/src/components/AppDialog.js index 5c97706a6..4d6517353 100644 --- a/cogboard-webapp/src/components/AppDialog.js +++ b/cogboard-webapp/src/components/AppDialog.js @@ -9,7 +9,7 @@ const StyledDialog = styled(props => ( ))` .paper { - width: 500px; + width: 700px; max-width: calc(100vw - 30px); padding: 15px; } From 7fcf21efc96b3fa6b4ee6d08e3c29c0aee21239b Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 12:00:16 +0100 Subject: [PATCH 180/226] Update placement of select menu items #467 --- .../Toolbar/FilterPicker/styled.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js index e1812ca87..509750fdb 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js @@ -1,6 +1,17 @@ import styled from '@emotion/styled/macro'; import { Box, Select, Chip } from '@material-ui/core'; +const selectDefaultProps = { + size: 'small', + MenuProps: { + anchorOrigin: { + vertical: 'bottom', + horizontal: 'left' + }, + getContentAnchorEl: null + } +}; + export const ScrollableBox = styled(Box)` overflow-x: scroll; scrollbar-width: none; @@ -13,16 +24,12 @@ export const ScrollableBox = styled(Box)` export const LogLevelSelect = styled(Select)` width: 6rem; `; -LogLevelSelect.defaultProps = { - size: 'small' -}; +LogLevelSelect.defaultProps = selectDefaultProps; export const FiltersSelect = styled(Select)` width: 12rem; `; -FiltersSelect.defaultProps = { - size: 'small' -}; +FiltersSelect.defaultProps = selectDefaultProps; export const StyledChip = styled(Chip)` margin-right: 0.25rem; From 133f5cdf33d98bba8585c1330e29c8b9a29c02ed Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 12:30:02 +0100 Subject: [PATCH 181/226] Fix layout twitching (filters selection) #467 --- .../LogViewerWidget/Toolbar/FilterPicker/styled.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js index 509750fdb..5961c9bd8 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/styled.js @@ -28,11 +28,21 @@ LogLevelSelect.defaultProps = selectDefaultProps; export const FiltersSelect = styled(Select)` width: 12rem; + & .MuiBox-root { + max-height: 19px; + } `; FiltersSelect.defaultProps = selectDefaultProps; export const StyledChip = styled(Chip)` margin-right: 0.25rem; + height: 18px; + position: relative; + top: -1px; + + & > svg { + margin-right: 2px; + } `; StyledChip.defaultProps = { size: 'small' From d09476ddc687e6fff406be261cbf72f312b055f1 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 12:58:41 +0100 Subject: [PATCH 182/226] Rename clear logs, update icon #467 --- .../Toolbar/DateRangePicker/index.js | 34 +++++++++++++++---- .../Toolbar/DateRangePicker/styled.js | 6 ++++ .../types/LogViewerWidget/Toolbar/index.js | 22 +++--------- .../cypress/integration/logsViewer.js | 10 +++--- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js index 81297c056..0a87fdcc6 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js @@ -1,12 +1,16 @@ import React from 'react'; +import moment from 'moment-timezone'; import MomentUtils from '@date-io/moment'; import { getDateSpan, saveDateSpan } from './helpers'; -import ToolbarGroup from '../ToolbarGroup'; import { MuiPickersUtilsProvider } from '@material-ui/pickers'; +import UpdateIcon from '@material-ui/icons/Update'; +import ToolbarGroup from '../ToolbarGroup'; +import ToggleIconButton from '../ToggleIconButton'; import CustomPicker from './CustomPicker'; +import { DatePickerWrapper } from './styled'; -const DateRangePicker = ({ widgetLocalStorage }) => { +const DateRangePicker = ({ widgetLocalStorage, lastLog }) => { const { begin, end } = getDateSpan(widgetLocalStorage); const handleBeginChange = date => @@ -14,14 +18,30 @@ const DateRangePicker = ({ widgetLocalStorage }) => { const handleEndChange = date => saveDateSpan(widgetLocalStorage, { begin, end: date }); + const handleClearLogs = () => { + const date = lastLog?.date; + if (date) { + const beginDate = moment(date).add(1, 'seconds'); + saveDateSpan(widgetLocalStorage, { begin: beginDate, end: null }); + } + }; + return ( - + + + + diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js index 5172040ae..b132d18ca 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/styled.js @@ -19,3 +19,9 @@ StyledIconButton.defaultProps = { export const CustomDateTimePicker = styled(DateTimePicker)` width: 10rem; `; + +export const DatePickerWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-end; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 38f09e0a5..9509a7561 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -1,6 +1,4 @@ import React from 'react'; -import moment from 'moment-timezone'; -import { saveDateSpan } from './DateRangePicker/helpers'; import ToggleIconButton from './ToggleIconButton'; import { Wrapper } from './styled'; @@ -8,7 +6,6 @@ import SearchInput from './SearchInput'; import ToolbarGroup from './ToolbarGroup'; import DateRangePicker from './DateRangePicker'; import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; -import DeleteIcon from '@material-ui/icons/Delete'; import FilterPicker from './FilterPicker'; const Toolbar = ({ @@ -20,14 +17,6 @@ const Toolbar = ({ handleFollowChange, lastLog }) => { - const handleClearLogs = () => { - const date = lastLog?.date; - if (date) { - const beginDate = moment(date).add(1, 'seconds'); - saveDateSpan(widgetLocalStorage, { begin: beginDate, end: null }); - } - }; - return ( @@ -44,7 +33,10 @@ const Toolbar = ({ quarantine={quarantine} /> - + - ); diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index bb3795aa6..73b296857 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -130,7 +130,7 @@ describe('Logs Viewer', () => { describe('Date span', () => { it('sets begin date on CLEAR LOGS button click', () => { - widget.click('[data-cy="clear-logs-button"'); + widget.click('[data-cy="show-logs-from-now-button"'); // begin date span picker should not be empty cy.get('[data-cy="date-time-picker-begin"] .MuiInput-root input').should( @@ -150,16 +150,16 @@ describe('Logs Viewer', () => { '' ); cy.get('[data-cy="log-entry"]').should('exist'); - }); }); - - describe('Quarantine', () => { + }); + + describe('Quarantine', () => { it('should allow logged in users to click quarantine button', () => { cy.get('[data-cy="advanced-filters-button"]').click(); cy.get('[data-cy="quarantine-show-dialog-button"]').should('exist'); cy.get('[data-cy="advanced-filters-menu-exit-button"]').click(); }); - + it('should not allow logged out users to click quarantine button', () => { cy.get('[data-cy="user-login-logout-icon"]').click(); cy.get('[data-cy="advanced-filters-button"]').click(); From 83f6367ad9e21f45c6cb5579e8c1f9ae46ee0779 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 15:02:54 +0100 Subject: [PATCH 183/226] Remove toolbar group #467 --- .../types/LogViewerWidget/LogList/styled.js | 2 +- .../Toolbar/DateRangePicker/CustomPicker.js | 6 +-- .../Toolbar/DateRangePicker/index.js | 41 ++++++++++--------- .../Toolbar/FilterPicker/index.js | 5 +-- .../Toolbar/ToolbarGroup/index.js | 17 -------- .../Toolbar/ToolbarGroup/styled.js | 14 ------- .../types/LogViewerWidget/Toolbar/index.js | 30 +++++--------- .../types/LogViewerWidget/Toolbar/styled.js | 2 + .../cypress/support/widgetAssertions.js | 2 +- 9 files changed, 42 insertions(+), 77 deletions(-) delete mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js delete mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index e5bc0d16b..f8bf8bd41 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -7,7 +7,7 @@ import logLevels from '../logLevels'; export const Container = styled.div` height: 100%; display: grid; - padding-top: 6em; + padding-top: 4.5em; grid-template-rows: auto 1fr; `; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js index 6e70cf026..7012bbbd9 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js @@ -7,12 +7,12 @@ import { CustomDateTimePicker } from './styled'; -const CustomPicker = ({ value, onChange, label, ...props }) => { +const CustomPicker = ({ id, value, onChange, label, ...props }) => { const handleChange = data => onChange(data?.seconds(0).milliseconds(0)); return ( { {value && ( onChange(null)} - data-cy={`date-time-picker-${label?.toLowerCase()}-clear`} + data-cy={`date-time-picker-${id}-clear`} > diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js index 0a87fdcc6..a82e63f3c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/DateRangePicker/index.js @@ -5,7 +5,6 @@ import { getDateSpan, saveDateSpan } from './helpers'; import { MuiPickersUtilsProvider } from '@material-ui/pickers'; import UpdateIcon from '@material-ui/icons/Update'; -import ToolbarGroup from '../ToolbarGroup'; import ToggleIconButton from '../ToggleIconButton'; import CustomPicker from './CustomPicker'; import { DatePickerWrapper } from './styled'; @@ -27,24 +26,28 @@ const DateRangePicker = ({ widgetLocalStorage, lastLog }) => { }; return ( - - - - - - - - - + + + + + + + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 0675fa402..2986191cb 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -10,7 +10,6 @@ import { StyledChip, FiltersWrapper } from './styled'; -import ToolbarGroup from '../ToolbarGroup'; import AdvancedFiltersMenu from './AdvancedFiltersMenu'; const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { @@ -37,7 +36,7 @@ const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { const handleLevelSelection = level => saveLevel(widgetLocalStorage, level); return ( - + <> Log level { quarantine={quarantine} /> - + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js deleted file mode 100644 index 554b0c029..000000000 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { string } from 'prop-types'; -import { Typography } from '@material-ui/core'; -import { Wrapper, GroupContainer } from './styled'; - -const ToolbarGroup = ({ title, children }) => ( - - {title} - {children} - -); - -ToolbarGroup.propTypes = { - title: string -}; - -export default ToolbarGroup; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js deleted file mode 100644 index bcb20e07a..000000000 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToolbarGroup/styled.js +++ /dev/null @@ -1,14 +0,0 @@ -import styled from '@emotion/styled/macro'; - -export const Wrapper = styled.div` - display: grid; - grid-template-rows: 1.7em auto; -`; - -export const GroupContainer = styled.div` - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: flex-end; - gap: 0.5em; -`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 9509a7561..0a2402f49 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -3,7 +3,6 @@ import React from 'react'; import ToggleIconButton from './ToggleIconButton'; import { Wrapper } from './styled'; import SearchInput from './SearchInput'; -import ToolbarGroup from './ToolbarGroup'; import DateRangePicker from './DateRangePicker'; import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; import FilterPicker from './FilterPicker'; @@ -19,33 +18,26 @@ const Toolbar = ({ }) => { return ( - - - - + - - - - handleFollowChange(!shouldFollowLogs)} - enabled={shouldFollowLogs} - Icon={ArrowDownwardIcon} - /> - + handleFollowChange(!shouldFollowLogs)} + enabled={shouldFollowLogs} + Icon={ArrowDownwardIcon} + /> ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index ea9f866e3..605d7ea18 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -10,8 +10,10 @@ export const Wrapper = styled.div` display: flex; flex-direction: row; justify-content: flex-start; + align-items: flex-end; gap: 1em; overflow-x: hidden; + padding: 0 10px; } `; diff --git a/functional/cypress-tests/cypress/support/widgetAssertions.js b/functional/cypress-tests/cypress/support/widgetAssertions.js index 9eec320b9..f3527c048 100644 --- a/functional/cypress-tests/cypress/support/widgetAssertions.js +++ b/functional/cypress-tests/cypress/support/widgetAssertions.js @@ -83,7 +83,7 @@ export function validateLinkList(widget) { } export function validateLogsViewer(widget) { - widget.assertText('p', 'Filters'); + widget.assertText('label', 'Log level'); widget.assertText('p', 'Level'); } From d949c1f6cbcd2be182e6dc361ec0dd4ffdd43f29 Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Sun, 9 Jan 2022 15:07:43 +0100 Subject: [PATCH 184/226] Delete comment after review --- .../src/components/widgets/types/LogViewerWidget/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 1ea697605..b724f67c3 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -90,7 +90,6 @@ const LogViewerWidget = ({ id }) => { {storedLogs && ( Date: Sun, 9 Jan 2022 15:30:25 +0100 Subject: [PATCH 185/226] Add toolbar layout stretch #467 --- .../LogViewerWidget/Toolbar/FilterPicker/index.js | 12 +++++++----- .../Toolbar/FilterPicker/styled.js | 15 +++++++++++---- .../LogViewerWidget/Toolbar/SearchInput/styled.js | 2 ++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js index 2986191cb..efb9c05c9 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/index.js @@ -2,10 +2,12 @@ import React from 'react'; import { getFilters, getLevel, saveFilters, saveLevel } from './helpers'; import logLevels from '../../logLevels'; -import { MenuItem, FormControl, InputLabel, Tooltip } from '@material-ui/core'; +import { MenuItem, InputLabel, Tooltip } from '@material-ui/core'; import { ScrollableBox, + FiltersFormControl, FiltersSelect, + LogLevelFormControl, LogLevelSelect, StyledChip, FiltersWrapper @@ -37,7 +39,7 @@ const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { return ( <> - + Log level { ))} - + - + {regExpFilters.length > 0 ? `Filters` : `No filters defined`} @@ -97,7 +99,7 @@ const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { ))} - + input { margin-right: 1.8rem; From 5588033f334b1d65f1769a3e3797cb2f984cfb6d Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 15:52:27 +0100 Subject: [PATCH 186/226] Update button sizes in log desc #467 --- .../widgets/types/LogViewerWidget/LogList/styled.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index f8bf8bd41..f8231dff7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -132,6 +132,9 @@ export const SimilarLogsButtonsContainer = styled.div` export const FilterSimilarLogsButton = styled(IconButton)` background-color: ${COLORS.LIGHT_SHADE}; `; +FilterSimilarLogsButton.defaultProps = { + size: 'small' +}; export const QuarantineSimilarLogsButton = styled(IconButton)` background-color: ${COLORS.RED}; @@ -139,3 +142,6 @@ export const QuarantineSimilarLogsButton = styled(IconButton)` background-color: ${COLORS.DARK_RED}; } `; +QuarantineSimilarLogsButton.defaultProps = { + size: 'small' +}; From 0870394fd3c532c455caa083734e754a9615396c Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 16:01:10 +0100 Subject: [PATCH 187/226] Remove static toolbar height #467 --- .../components/widgets/types/LogViewerWidget/LogList/index.js | 2 -- .../widgets/types/LogViewerWidget/LogList/styled.js | 3 +-- .../widgets/types/LogViewerWidget/Toolbar/styled.js | 4 +--- .../src/components/widgets/types/LogViewerWidget/styled.js | 2 ++ 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 5b197543b..828a68a7f 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -23,8 +23,6 @@ export default function LogList({ const scrollerRef = useRef(null); const [scroll, setScroll] = useState(0); - console.log(template); - const VariableLogListHeader = () => ( {template?.map((name, index) => ( diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index f8231dff7..83163a14b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -5,9 +5,8 @@ import { Typography, Accordion, IconButton } from '@material-ui/core'; import logLevels from '../logLevels'; export const Container = styled.div` - height: 100%; + flex-grow: 1; display: grid; - padding-top: 4.5em; grid-template-rows: auto 1fr; `; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index 605d7ea18..6f2364b79 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -4,8 +4,6 @@ import { COLORS } from '../../../../../constants'; export const Wrapper = styled.div` width: 100%; - top: 0; - position: absolute; overflow-x: scroll; display: flex; flex-direction: row; @@ -13,7 +11,7 @@ export const Wrapper = styled.div` align-items: flex-end; gap: 1em; overflow-x: hidden; - padding: 0 10px; + padding: 0 10px 1.1rem 10px; } `; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js index a101a09c6..cea6151ba 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/styled.js @@ -2,6 +2,8 @@ import styled from '@emotion/styled/macro'; export const Container = styled.div` position: relative; + display: flex; + flex-direction: column; height: 100%; width: 100%; min-width: 54rem; From f3bac48f14be4e2d0677a32a1bfa886f026cd5f2 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sun, 9 Jan 2022 20:00:18 +0100 Subject: [PATCH 188/226] Add log parser field to initData --- cogboard-app/src/main/resources/initData/config.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cogboard-app/src/main/resources/initData/config.json b/cogboard-app/src/main/resources/initData/config.json index 8c65cc9b3..98fc815c9 100644 --- a/cogboard-app/src/main/resources/initData/config.json +++ b/cogboard-app/src/main/resources/initData/config.json @@ -149,9 +149,7 @@ "switchInterval": 60, "id": "board-7d6e23ea-78a1-4f89-a5d2-47c499f9657d", "theme": "default", - "widgets": [ - "widget95" - ], + "widgets": ["widget95"], "columns": 4, "title": "Log Viewer", "type": "WidgetBoard" @@ -1982,7 +1980,8 @@ "path": "/home/mock/example.txt", "logLinesField": 300, "logFileSizeField": 50, - "logRecordExpirationField": 5 + "logRecordExpirationField": 5, + "logParserField": "default" } } } From 2d2645b848febee04b9a578046b1b32d62fab5fc Mon Sep 17 00:00:00 2001 From: Borrubasz Date: Sun, 9 Jan 2022 21:04:20 +0100 Subject: [PATCH 189/226] Add button redirecting to documentation from quarantine form --- .../Toolbar/QuarantineModal/QuarantineForm.js | 13 +++++++++++-- cogboard-webapp/src/constants/index.js | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js index 367636ce8..c6017d6cf 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js @@ -2,11 +2,12 @@ import React, { useEffect, useContext } from 'react'; import { createValidationSchema } from '../../../../../validation'; import { useFormData } from '../../../../../../hooks'; import DynamicForm from '../../../../../DynamicForm'; -import { Button } from '@material-ui/core'; +import { Button, Tooltip, IconButton } from '@material-ui/core'; import InfoIcon from '@material-ui/icons/Info'; import { StyledHorizontalContainer, StyledCancelButton } from './styled'; import dialogFields from '../../../../dialogFields'; import { SimilarLogsContext } from '../../context'; +import { URL } from '../../../../../../constants'; const QuarantineForm = ({ filters, @@ -49,7 +50,15 @@ const QuarantineForm = ({ return ( - + + + + +

Logs, any fields of which will be matched by the regular expression, will not be stored in the database or displayed. diff --git a/cogboard-webapp/src/constants/index.js b/cogboard-webapp/src/constants/index.js index 4b2cc8e22..ff80aabe2 100644 --- a/cogboard-webapp/src/constants/index.js +++ b/cogboard-webapp/src/constants/index.js @@ -33,7 +33,9 @@ export const URL = { UPDATE_USER_SETTINGS: '/api/user/update', UPDATE_INFO: 'https://github.com/wttech/cogboard/wiki#update', CREDENTIAL_INFO: 'https://github.com/wttech/cogboard/wiki#credentials', - LOGS_ENDPOINT: '/api/logs' + LOGS_ENDPOINT: '/api/logs', + LOGSVIEWER_QUARANTINE: + 'https://github.com/wttech/cogboard/wiki/4.-Widgets#LogsViewer' }; export const COLUMN_MULTIPLIER = 2; export const ROW_MULTIPLIER = 2; From 93dc7aec20eab9dc45f705acc78a10f2ffae41db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Sun, 9 Jan 2022 23:15:27 +0100 Subject: [PATCH 190/226] Deleted remaining unnecessary changes --- .gitignore | 1 - cogboard-local-compose.yml | 4 ++-- docker/Dockerfile | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 863c97733..26d5544f2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ Thumbs.db # Project specific /mnt/*.json /mnt/*.backup -/mnt/init-mongo.js /mnt/content/*.json /mnt/.version /cogboard-compose.yml diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 02a7210bf..0a95e07ac 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -34,8 +34,8 @@ services: - "./mnt:/data" networks: - cognet - ports: - - "18092:18092" +# ports: +# - "18092:18092" mongo: image: mongo:4 diff --git a/docker/Dockerfile b/docker/Dockerfile index 8df979145..28f18960d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ LABEL maintainer="CogBoard Project" COPY ./out/knotx /usr/local/knotx -RUN sed -i 's/# JVM_DEBUG=/JVM_DEBUG=/g' /usr/local/knotx/bin/knotx -RUN sed -i 's/suspend=n/suspend=y/g' /usr/local/knotx/bin/knotx +#RUN sed -i 's/# JVM_DEBUG=/JVM_DEBUG=/g' /usr/local/knotx/bin/knotx +#RUN sed -i 's/suspend=n/suspend=y/g' /usr/local/knotx/bin/knotx CMD [ "knotx", "run-knotx" ] \ No newline at end of file From a112f258bba7083506c601aaba4227cd3d957efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Mon, 10 Jan 2022 01:41:18 +0100 Subject: [PATCH 191/226] Set up Log Viewer credentials with SSH key and changed CRLF to LF #469 --- .gitignore | 1 + cogboard-app/build.gradle.kts | 92 ++--- .../src/main/resources/initData/admin.json | 8 +- .../main/resources/initData/credentials.json | 9 + .../cogboard/widget/type/ZabbixWidgetTest.kt | 228 +++++------ .../widget/type/zabbix/ZabbixUtilTest.kt | 100 ++--- .../cogboard/config/credentials-test.json | 44 +-- .../cogboard/config/endpoints-test.json | 34 +- .../src/components/FormField/index.js | 62 +-- .../src/components/SemiProgressBar/index.js | 183 +++++---- .../src/components/SemiProgressBar/styled.js | 42 +- .../src/components/ZabbixChart/helpers.js | 18 +- .../src/components/ZabbixChart/index.js | 290 +++++++------- .../src/components/ZabbixChart/styled.js | 140 +++---- .../widgets/dialogFields/LinkListInput.js | 354 ++++++++--------- .../widgets/dialogFields/MaxValueInput.js | 92 +++-- .../widgets/dialogFields/RangeSlider.js | 106 +++--- .../widgets/dialogFields/ToDoListinput.js | 358 +++++++++--------- .../widgets/types/LinkListWidget/index.js | 88 ++--- .../widgets/types/LinkListWidget/styled.js | 26 +- .../widgets/types/TextWidget/index.js | 172 ++++----- .../widgets/types/ZabbixWidget/index.js | 252 ++++++------ .../widgets/types/ZabbixWidget/styled.js | 80 ++-- functional/cypress-tests/README.md | 206 +++++----- .../cypress/integration/refresh_widgets.js | 114 +++--- .../cypress-tests/cypress/support/widgets.js | 118 +++--- 26 files changed, 1636 insertions(+), 1581 deletions(-) diff --git a/.gitignore b/.gitignore index 26d5544f2..220269953 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ Thumbs.db # Project specific /mnt/*.json /mnt/*.backup +/mnt/widget* /mnt/content/*.json /mnt/.version /cogboard-compose.yml diff --git a/cogboard-app/build.gradle.kts b/cogboard-app/build.gradle.kts index 279ae8929..ea4dc4b39 100644 --- a/cogboard-app/build.gradle.kts +++ b/cogboard-app/build.gradle.kts @@ -1,47 +1,47 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - kotlin("jvm") -} - -tasks.named("test") { - useJUnitPlatform() -} - -dependencies { - - "io.knotx:knotx".let { v -> - implementation(platform("$v-dependencies:${project.property("knotx.version")}")) - implementation("$v-server-http-api:${project.property("knotx.version")}") - } - "io.vertx:vertx".let { v -> - implementation("$v-web") - implementation("$v-auth-jwt") - implementation("$v-web-client") - implementation("$v-rx-java2") - } - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.0") - implementation(kotlin("stdlib-jdk8")) - implementation("com.jcraft:jsch:0.1.55") - implementation("org.mongodb:mongo-java-driver:3.12.10") - - testImplementation("org.assertj:assertj-core:3.12.2") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.4.2") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.3.1") - testImplementation("org.mockito:mockito-junit-jupiter:3.1.0") - testImplementation(gradleTestKit()) - testRuntime("org.junit.jupiter:junit-jupiter-engine:5.4.2") -} - -repositories { - mavenCentral() -} - -val compileKotlin: KotlinCompile by tasks -compileKotlin.kotlinOptions { - jvmTarget = "1.8" -} -val compileTestKotlin: KotlinCompile by tasks -compileTestKotlin.kotlinOptions { - jvmTarget = "1.8" +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") +} + +tasks.named("test") { + useJUnitPlatform() +} + +dependencies { + + "io.knotx:knotx".let { v -> + implementation(platform("$v-dependencies:${project.property("knotx.version")}")) + implementation("$v-server-http-api:${project.property("knotx.version")}") + } + "io.vertx:vertx".let { v -> + implementation("$v-web") + implementation("$v-auth-jwt") + implementation("$v-web-client") + implementation("$v-rx-java2") + } + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.0") + implementation(kotlin("stdlib-jdk8")) + implementation("com.jcraft:jsch:0.1.55") + implementation("org.mongodb:mongo-java-driver:3.12.10") + + testImplementation("org.assertj:assertj-core:3.12.2") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.4.2") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.3.1") + testImplementation("org.mockito:mockito-junit-jupiter:3.1.0") + testImplementation(gradleTestKit()) + testRuntime("org.junit.jupiter:junit-jupiter-engine:5.4.2") +} + +repositories { + mavenCentral() +} + +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" } \ No newline at end of file diff --git a/cogboard-app/src/main/resources/initData/admin.json b/cogboard-app/src/main/resources/initData/admin.json index 3fbc3f7fe..dd8939d32 100644 --- a/cogboard-app/src/main/resources/initData/admin.json +++ b/cogboard-app/src/main/resources/initData/admin.json @@ -1,4 +1,4 @@ -{ - "user": "admin", - "password": "admin" -} +{ + "user": "admin", + "password": "admin" +} diff --git a/cogboard-app/src/main/resources/initData/credentials.json b/cogboard-app/src/main/resources/initData/credentials.json index c02debd75..836427659 100644 --- a/cogboard-app/src/main/resources/initData/credentials.json +++ b/cogboard-app/src/main/resources/initData/credentials.json @@ -16,6 +16,15 @@ "user": "mock", "label": "SSH Cred", "id": "credential4" + }, + { + "sshKeyPassphrase": "", + "sshKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIG4wIBAAKCAYEArja5epueCYkWwrakBEJcwdy82o+MMzYqzL8ScvZUQXjzXdQL\nKqG+pE2Ng8Cldp+/X+APL5qc/kPzDJJYxUhAEV/x/aj/yn2cdTjzRc8cevS1UCWU\nArC8Gm1R6yqhhbvsH81lgNaQYE6OxIiLE5DD/0UqmabeHzvBdOr4lDILk3kjONCW\nhvKAto5zjEi9GRwfDYP2IqxruC9OCY4I1tJNt0gDvL+CCTzpLuZeJVy6iWJAFZGc\nIKw/Zs8tTG7bjojWXDJC1vTz2NezGEay2crta0G/YZ2ak9kOGNHcWBuTw23x4YqX\nja+oA0WpX887cNygTFTX5Q/KzXBjd4m8NruF0phd8iKS3L6NfeZmHoOp1Zo+jk4H\nRrsuSZGn3uRM6xIRTJthYI+33iis7c4aQytq/h0rUjdwUSEppJxf0FjJlkV4iCK1\nsXlT6lKCbEiz4UEKtPvnvCGfT/UPYNnrbLtpy94xyAfSWzPlm5Gqb07Nme5O4d5v\n05f3RDXCpa5CJYB7AgMBAAECggGAUKVgo1Nai0t8z9JAhwA5dDy85+g/nI1crr9c\nyP8i7dQRxMOeD7QkTmbgNbd+YTV+H+HW5dCLEGFgJ9evZFQX5HMn0KblElWnkdQ1\nOYGwy3JwZJOgusYZrZohq91mPERMAETS0huBZjO3f18+EmaXdJoOKGbIuGivG3KS\nc/fex/vxxCE7LWkhEGFNOAmMEA2milkmHdL3YqHzXBT2HovoEgoyQLPefGxH/cAC\noQUWDPcAd3uabL2P9AXAEHvZA1hwL0I0x3wxJH9YcAf97kAT4//0ImhOqr7Nu69b\nt8ukP0uyqHE4zFMbbVVJYfqwtduHXt6qYQIR8T7W8EynU/jsgsFqBFb9sP0+KpsI\n0GDPVvyiS/kgX1/yp/dyXw25ngo9GRKPCDI+yIotkB4ank38uKEbXc4CHQ9ueZAZ\nw2WkqpUB9HAykPbyOeaEc4T93rLlzqNnFembsRdTc7X0W4nsVvPZXv/pae3OSMED\nORf4j0QQmgiIXyfZ75QkvJ2WC/phAoHBANsSmqIAst38ZjRFyVCCaaGdgMjAusEp\ni2urm/hnjYr5H0PjnLxU3G961qTnOZqT/VJ8jFjPvUCcnFFOW/GTIIW3g+/BSFui\nRlp+vvyj/HqaEWVU46JpG/AItlFT0Ktg2yiKZ/3bABZsc5DqAB0fGGMKNs4cXwwt\nsNTfullQzaRW8pUp3UE+uEWHvyhAZvAIf93+X0k/G1prSLHnIc0FKTs52P5gpyl+\nSfkjAoyRs1fUOd/PQIh2t5+/NAZX39O7WQKBwQDLlF/xtbiT+SWkH06kN9bwCXgC\nDOmnMwIfrmHud5bPiRMvKaTrjD7bbVaKvXSktyI9DALYtsVxRzY4lIVgWAAT3rxK\ntE5vzdwb0TQbXNGomWM13ynQgClEz0g1sAFJ1hO8R1DRBFmV8P8AV80ni4d92GkX\nZHbSwF+shxGudW2J/fGgQkibWkIfnPT3gIi4tjexbg0WT60ZI5neEplpc5dJPF4q\nBiV1Hu660yOKAjtTZSIR1EyTrh/nClNHNJvJo/MCgcEAtuXKWfSRYMnHnl6hG2k1\nvWtcyL43bOs9bjAA8JurzVn9o1VVVtrWivAYYeZ17jsdpI89MSyHCXl2/F6aXo6B\n+YFkUneg7HgHmqf01cInGUilu17rCX4NiBIN/MooDdy4PBmJhqQfZ5k1xsfGPonm\nd1FgviVrqSRAXQlIcCcI+OpqbuRbx4wQlmQl0Portryx3GnxrZpVQOEO+RBJ5Pwp\nFzxNkNqq1PaN1cVH7In8HBigFN3YN9Y9qc4dJiqZQRFJAoHAamhIad9w9a8hVJKk\nmUMyjk50sqWrLyCDOKn+OBW79wgPxfP/ZrrsU+bneCcko7+xHrV7e2i09Mui9Jn0\nyPHWQIyIYIe0A85XARctJCw0zeo2p/7YLUn/yB6MALvZQI2rzRp9jHK4nJ3Vu4kp\nC0Vr8YQ/EeIKFYhFubjzrftk4N6iAAEFUGYx77IrfH5reBiOLah3ILVOpbgtAZ05\nIJwxdC8gjNiflYMwhug7SDR4a9ONpkIQMJSvyiRkePBviUqvAoHAOwnmneFwMpfz\nSzrHDClrhu8+r7f0HLk7e0rI/vQ8LAlFIonsNxZGIgtR0itrZQX/fdwlCnwakYEf\npXmViDS4ftOFuquCO331l9s9oF192aEB8ms+e4FzRVVhHd61U8TaiGU8pJnTw58A\naaMXL+ir+e+XRaAr0+zejZnYkPy9IkBROC/IZeawy9mFpbrW5AhlBfksexqtPlp8\nNNxWGwzQrXuffeTWLJBclPF8IJE6DZIw53UyHoD5Fg4asS9Wc1yV\n-----END RSA PRIVATE KEY-----\n", + "token": "", + "password": "", + "user": "mock", + "label": "SSH Key Credentials", + "id": "credential5" } ] } diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/ZabbixWidgetTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/ZabbixWidgetTest.kt index 59d70d850..09ef9e54f 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/ZabbixWidgetTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/ZabbixWidgetTest.kt @@ -1,115 +1,115 @@ -package com.cognifide.cogboard.widget.type - -import com.cognifide.cogboard.widget.type.zabbix.ZabbixWidget -import io.vertx.core.json.JsonArray -import io.vertx.core.json.JsonObject -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.junit.jupiter.MockitoExtension -import com.cognifide.cogboard.TestHelper.Companion.readConfigFromResource as load - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@ExtendWith(MockitoExtension::class) -class ZabbixWidgetTest : WidgetTestBase() { - - private lateinit var zabbixWidgetTest: ZabbixWidget - - override fun widgetName(): String = zabbixWidgetTest.javaClass.simpleName - - override fun initWidget(): JsonObject = super.initWidget() - .put(RANGE, JsonArray().add(20).add(60)) - .put(MAX_VALUE, 100) - - @BeforeEach - fun initTest() { - super.init() - } - - @Test - fun `Expect ok widget update message send on event bus`() { - initZabbixWidgetWithMetric(discUsedMetric) - val (result, content) = handleResponse(jsonFileDiscUsedMetric) - - assertUpdateDatePresent(result) - assertLastValue("7254701317", content) - assertName("Used disk space on \$1", content) - assertStatus("OK", result) - assertHistory("7254701317", "1602331216799", content) - } - - @Test - fun `Expect warn widget update message send on event bus`() { - initZabbixWidgetWithMetric(heapMetric) - val (result, content) = handleResponse(jsonFileHeapMetric) - - assertUpdateDatePresent(result) - assertLastValue("50744555432", content) - assertName("mem heap size", content) - assertStatus("UNSTABLE", result) - assertHistory("50744555432", "1602486782740", content) - } - - @Test - fun `Expect fail widget update message send on event bus`() { - initZabbixWidgetWithMetric(cpuMetric) - val (result, content) = handleResponse(jsonFileCpuMetric) - - assertUpdateDatePresent(result) - assertLastValue("63", content) - assertName("CPU \$2 time", content) - assertStatus("FAIL", result) - assertHistory("63", "1602331143732", content) - } - - @Test - fun `Expect unknown widget update message send on event bus`() { - initZabbixWidgetWithMetric(uptimeMetric) - val (result, content) = handleResponse(jsonFileUptimeMetric) - - assertUpdateDatePresent(result) - assertLastValue("88326792", content) - assertName("System uptime", content) - assertStatus("NONE", result) - assertHistory("88326792", "1602331294376", content) - } - - private fun initZabbixWidgetWithMetric(metric: String = "") { - val config = initWidget().put(METRIC, metric) - zabbixWidgetTest = ZabbixWidget(vertx, config, initService()) - } - - private fun handleResponse(jsonFile: String): Pair { - val response = load("/com/cognifide/cogboard/widget/type/${widgetName()}/${jsonFile}") - zabbixWidgetTest.handleResponse(response) - return captureWhatIsSent(eventBus, captor) - } - - private fun assertLastValue(expected: String, content: JsonObject) { - Assertions.assertEquals(expected, content.getString("lastvalue")) - } - - private fun assertName(expected: String, content: JsonObject) { - Assertions.assertEquals(expected, content.getString("name")) - } - - private fun assertHistory(expected: String, key: String, content: JsonObject) { - Assertions.assertEquals(expected, content.getJsonObject("history").map[key]) - } - - companion object { - private const val METRIC = "selectedZabbixMetric" - private const val MAX_VALUE = "maxValue" - private const val RANGE = "range" - private const val cpuMetric = "system.cpu.util[,idle]" - private const val jsonFileCpuMetric = "cpu.json" - private const val discUsedMetric = "vfs.fs.size[/,used]" - private const val heapMetric = "jmx[\\\"java.lang:type=Memory\\\",\\\"HeapMemoryUsage.used\\\"]" - private const val jsonFileDiscUsedMetric = "disc_used.json" - private const val uptimeMetric = "system.uptime" - private const val jsonFileUptimeMetric = "uptime.json" - private const val jsonFileHeapMetric = "heap.json" - } +package com.cognifide.cogboard.widget.type + +import com.cognifide.cogboard.widget.type.zabbix.ZabbixWidget +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.junit.jupiter.MockitoExtension +import com.cognifide.cogboard.TestHelper.Companion.readConfigFromResource as load + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(MockitoExtension::class) +class ZabbixWidgetTest : WidgetTestBase() { + + private lateinit var zabbixWidgetTest: ZabbixWidget + + override fun widgetName(): String = zabbixWidgetTest.javaClass.simpleName + + override fun initWidget(): JsonObject = super.initWidget() + .put(RANGE, JsonArray().add(20).add(60)) + .put(MAX_VALUE, 100) + + @BeforeEach + fun initTest() { + super.init() + } + + @Test + fun `Expect ok widget update message send on event bus`() { + initZabbixWidgetWithMetric(discUsedMetric) + val (result, content) = handleResponse(jsonFileDiscUsedMetric) + + assertUpdateDatePresent(result) + assertLastValue("7254701317", content) + assertName("Used disk space on \$1", content) + assertStatus("OK", result) + assertHistory("7254701317", "1602331216799", content) + } + + @Test + fun `Expect warn widget update message send on event bus`() { + initZabbixWidgetWithMetric(heapMetric) + val (result, content) = handleResponse(jsonFileHeapMetric) + + assertUpdateDatePresent(result) + assertLastValue("50744555432", content) + assertName("mem heap size", content) + assertStatus("UNSTABLE", result) + assertHistory("50744555432", "1602486782740", content) + } + + @Test + fun `Expect fail widget update message send on event bus`() { + initZabbixWidgetWithMetric(cpuMetric) + val (result, content) = handleResponse(jsonFileCpuMetric) + + assertUpdateDatePresent(result) + assertLastValue("63", content) + assertName("CPU \$2 time", content) + assertStatus("FAIL", result) + assertHistory("63", "1602331143732", content) + } + + @Test + fun `Expect unknown widget update message send on event bus`() { + initZabbixWidgetWithMetric(uptimeMetric) + val (result, content) = handleResponse(jsonFileUptimeMetric) + + assertUpdateDatePresent(result) + assertLastValue("88326792", content) + assertName("System uptime", content) + assertStatus("NONE", result) + assertHistory("88326792", "1602331294376", content) + } + + private fun initZabbixWidgetWithMetric(metric: String = "") { + val config = initWidget().put(METRIC, metric) + zabbixWidgetTest = ZabbixWidget(vertx, config, initService()) + } + + private fun handleResponse(jsonFile: String): Pair { + val response = load("/com/cognifide/cogboard/widget/type/${widgetName()}/${jsonFile}") + zabbixWidgetTest.handleResponse(response) + return captureWhatIsSent(eventBus, captor) + } + + private fun assertLastValue(expected: String, content: JsonObject) { + Assertions.assertEquals(expected, content.getString("lastvalue")) + } + + private fun assertName(expected: String, content: JsonObject) { + Assertions.assertEquals(expected, content.getString("name")) + } + + private fun assertHistory(expected: String, key: String, content: JsonObject) { + Assertions.assertEquals(expected, content.getJsonObject("history").map[key]) + } + + companion object { + private const val METRIC = "selectedZabbixMetric" + private const val MAX_VALUE = "maxValue" + private const val RANGE = "range" + private const val cpuMetric = "system.cpu.util[,idle]" + private const val jsonFileCpuMetric = "cpu.json" + private const val discUsedMetric = "vfs.fs.size[/,used]" + private const val heapMetric = "jmx[\\\"java.lang:type=Memory\\\",\\\"HeapMemoryUsage.used\\\"]" + private const val jsonFileDiscUsedMetric = "disc_used.json" + private const val uptimeMetric = "system.uptime" + private const val jsonFileUptimeMetric = "uptime.json" + private const val jsonFileHeapMetric = "heap.json" + } } \ No newline at end of file diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/zabbix/ZabbixUtilTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/zabbix/ZabbixUtilTest.kt index a5dfaeecb..ab914e7b3 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/zabbix/ZabbixUtilTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/zabbix/ZabbixUtilTest.kt @@ -1,50 +1,50 @@ -package com.cognifide.cogboard.widget.type.zabbix - -import com.cognifide.cogboard.widget.Widget -import io.vertx.core.json.JsonArray -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -internal class ZabbixUtilTest { - - @Test - fun `Expect percentage value after conversion`() { - val percentage = SAMPLE_VALUE_IN_BYTES.convertToPercentage(MAX_VALUE_100_GB) - - assertEquals(51, percentage) - } - - @Test - fun `Expect OK status for value less than start of range value`() { - val status = status(59, range) - - assertEquals(Widget.Status.OK, status) - } - - @Test - fun `Expect UNSTABLE status for start of range value`() { - val status = status(60, range) - - assertEquals(Widget.Status.UNSTABLE, status) - } - - @Test - fun `Expect UNSTABLE status for value less than end of range value`() { - val status = status(79, range) - - assertEquals(Widget.Status.UNSTABLE, status) - } - - @Test - fun `Expect FAIL status for end of range value`() { - val status = status(80, range) - - assertEquals(Widget.Status.FAIL, status) - } - - companion object { - const val MAX_VALUE_100_GB = 100 - const val SAMPLE_VALUE_IN_BYTES = 50744555432L - val range: JsonArray = JsonArray().add(60).add(80) - } -} +package com.cognifide.cogboard.widget.type.zabbix + +import com.cognifide.cogboard.widget.Widget +import io.vertx.core.json.JsonArray +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +internal class ZabbixUtilTest { + + @Test + fun `Expect percentage value after conversion`() { + val percentage = SAMPLE_VALUE_IN_BYTES.convertToPercentage(MAX_VALUE_100_GB) + + assertEquals(51, percentage) + } + + @Test + fun `Expect OK status for value less than start of range value`() { + val status = status(59, range) + + assertEquals(Widget.Status.OK, status) + } + + @Test + fun `Expect UNSTABLE status for start of range value`() { + val status = status(60, range) + + assertEquals(Widget.Status.UNSTABLE, status) + } + + @Test + fun `Expect UNSTABLE status for value less than end of range value`() { + val status = status(79, range) + + assertEquals(Widget.Status.UNSTABLE, status) + } + + @Test + fun `Expect FAIL status for end of range value`() { + val status = status(80, range) + + assertEquals(Widget.Status.FAIL, status) + } + + companion object { + const val MAX_VALUE_100_GB = 100 + const val SAMPLE_VALUE_IN_BYTES = 50744555432L + val range: JsonArray = JsonArray().add(60).add(80) + } +} diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json index b5b431094..e5fafd1ad 100644 --- a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json +++ b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/credentials-test.json @@ -1,22 +1,22 @@ -{ - "credentials": [ - { - "id": "credentials1", - "label": "My Credentials 1", - "user": "user1", - "password": "password1", - "token": "token1", - "sshKey": "key1", - "sshKeyPassphrase": "pass1" - }, - { - "id": "credentials2", - "label": "My Credentials 2", - "user": "user2", - "password": "password2", - "token": "token2", - "sshKey": "key2", - "sshKeyPassphrase": "pass2" - } - ] -} +{ + "credentials": [ + { + "id": "credentials1", + "label": "My Credentials 1", + "user": "user1", + "password": "password1", + "token": "token1", + "sshKey": "key1", + "sshKeyPassphrase": "pass1" + }, + { + "id": "credentials2", + "label": "My Credentials 2", + "user": "user2", + "password": "password2", + "token": "token2", + "sshKey": "key2", + "sshKeyPassphrase": "pass2" + } + ] +} diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/endpoints-test.json b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/endpoints-test.json index f85a096c0..84cfe0013 100644 --- a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/endpoints-test.json +++ b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/endpoints-test.json @@ -1,17 +1,17 @@ -{ - "endpoints": [ - { - "id": "validEndpoint", - "label": "Valid Endpoint", - "url": "url", - "publicUrl": "Public Url", - "credentials": "credentials1" - }, - { - "id": "invalidEndpoint", - "label": "Invalid Endpoint", - "url": "url", - "credentials": "nonExistingCredentials" - } - ] -} +{ + "endpoints": [ + { + "id": "validEndpoint", + "label": "Valid Endpoint", + "url": "url", + "publicUrl": "Public Url", + "credentials": "credentials1" + }, + { + "id": "invalidEndpoint", + "label": "Invalid Endpoint", + "url": "url", + "credentials": "nonExistingCredentials" + } + ] +} diff --git a/cogboard-webapp/src/components/FormField/index.js b/cogboard-webapp/src/components/FormField/index.js index 5ecdd2007..30730b9fe 100644 --- a/cogboard-webapp/src/components/FormField/index.js +++ b/cogboard-webapp/src/components/FormField/index.js @@ -1,31 +1,31 @@ -import React from 'react'; - -import { camelToKebab, createValueRef } from './helpers'; -import dialogFields from '../widgets/dialogFields'; - -const FormField = ({ field, values, handleChange, errors, rootName }) => { - const { - component: DialogField, - name, - initialValue = '', - valueUpdater, - validator, - ...dialogFieldProps - } = dialogFields[field]; - - const valueRef = createValueRef(values, initialValue, name); - - return ( - - ); -}; - -export default FormField; +import React from 'react'; + +import { camelToKebab, createValueRef } from './helpers'; +import dialogFields from '../widgets/dialogFields'; + +const FormField = ({ field, values, handleChange, errors, rootName }) => { + const { + component: DialogField, + name, + initialValue = '', + valueUpdater, + validator, + ...dialogFieldProps + } = dialogFields[field]; + + const valueRef = createValueRef(values, initialValue, name); + + return ( + + ); +}; + +export default FormField; diff --git a/cogboard-webapp/src/components/SemiProgressBar/index.js b/cogboard-webapp/src/components/SemiProgressBar/index.js index 8a14ee271..2045c260e 100644 --- a/cogboard-webapp/src/components/SemiProgressBar/index.js +++ b/cogboard-webapp/src/components/SemiProgressBar/index.js @@ -1,93 +1,90 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { StyledSemiCircleContainer, StyledPercentageText } from './styled'; - -const SemiCircleProgress = ({ - stroke, - strokeWidth, - background, - diameter, - showPercentValue, - percentage, - text -}) => { - const coordinateForCircle = diameter / 2; - const radius = (diameter - 2 * strokeWidth) / 2; - const circumference = Math.PI * radius; - - const setPercentageValue = () => { - let percentageValue; - - if (percentage > 100) { - percentageValue = 100; - } else if (percentage < 0) { - percentageValue = 0; - } else { - percentageValue = percentage; - } - - return percentageValue; - } - - const semiCirclePercentage = setPercentageValue() * (circumference / 100); - - return ( - - - - - - {showPercentValue && ( - - {text ? `${text}GB/(${percentage}%)` : `${percentage}%`} - - )} - - ); -}; - -SemiCircleProgress.propTypes = { - strokeWidth: PropTypes.number, - diameter: PropTypes.number, - showPercentValue: PropTypes.bool, - stroke: PropTypes.string, - background: PropTypes.string, - text: PropTypes.number -}; - -SemiCircleProgress.defaultProps = { - strokeWidth: 10, - diameter: 200, - showPercentValue: false, - stroke: "#02B732", - background: "#D0D0CE", -}; - -export default SemiCircleProgress; +import React from 'react'; +import PropTypes from 'prop-types'; +import { StyledSemiCircleContainer, StyledPercentageText } from './styled'; + +const SemiCircleProgress = ({ + stroke, + strokeWidth, + background, + diameter, + showPercentValue, + percentage, + text +}) => { + const coordinateForCircle = diameter / 2; + const radius = (diameter - 2 * strokeWidth) / 2; + const circumference = Math.PI * radius; + + const setPercentageValue = () => { + let percentageValue; + + if (percentage > 100) { + percentageValue = 100; + } else if (percentage < 0) { + percentageValue = 0; + } else { + percentageValue = percentage; + } + + return percentageValue; + }; + + const semiCirclePercentage = setPercentageValue() * (circumference / 100); + + return ( + + + + + + {showPercentValue && ( + + {text ? `${text}GB/(${percentage}%)` : `${percentage}%`} + + )} + + ); +}; + +SemiCircleProgress.propTypes = { + strokeWidth: PropTypes.number, + diameter: PropTypes.number, + showPercentValue: PropTypes.bool, + stroke: PropTypes.string, + background: PropTypes.string, + text: PropTypes.number +}; + +SemiCircleProgress.defaultProps = { + strokeWidth: 10, + diameter: 200, + showPercentValue: false, + stroke: '#02B732', + background: '#D0D0CE' +}; + +export default SemiCircleProgress; diff --git a/cogboard-webapp/src/components/SemiProgressBar/styled.js b/cogboard-webapp/src/components/SemiProgressBar/styled.js index d05a4245c..e6d3564e6 100644 --- a/cogboard-webapp/src/components/SemiProgressBar/styled.js +++ b/cogboard-webapp/src/components/SemiProgressBar/styled.js @@ -1,21 +1,21 @@ -import styled from '@emotion/styled/macro'; - -export const StyledPercentageText = styled.span` - bottom: 0; - left: 0; - position: absolute; - text-align: center; - width: 100%; -`; - -export const StyledSemiCircleContainer = styled.div` - display: flex; - justify-content: center; - position: relative; - margin-bottom: 8px; - - svg { - overflow: hidden; - transform: rotateY(180deg); - } -`; +import styled from '@emotion/styled/macro'; + +export const StyledPercentageText = styled.span` + bottom: 0; + left: 0; + position: absolute; + text-align: center; + width: 100%; +`; + +export const StyledSemiCircleContainer = styled.div` + display: flex; + justify-content: center; + position: relative; + margin-bottom: 8px; + + svg { + overflow: hidden; + transform: rotateY(180deg); + } +`; diff --git a/cogboard-webapp/src/components/ZabbixChart/helpers.js b/cogboard-webapp/src/components/ZabbixChart/helpers.js index 5c0b24bce..a3ef0cc70 100644 --- a/cogboard-webapp/src/components/ZabbixChart/helpers.js +++ b/cogboard-webapp/src/components/ZabbixChart/helpers.js @@ -1,8 +1,10 @@ -export const calculatePercentageValue = (value, maxValue) => Math.round((100 * value) / maxValue) - -export const getNumberOfElements = (array, number) => array.slice(Math.max(array.length - number, 0)); - -export const convertEpochToDate = (value) => { - const convertedEpoch = parseInt(value) + (new Date().getTimezoneOffset() * -1); - return new Date(convertedEpoch); -} \ No newline at end of file +export const calculatePercentageValue = (value, maxValue) => + Math.round((100 * value) / maxValue); + +export const getNumberOfElements = (array, number) => + array.slice(Math.max(array.length - number, 0)); + +export const convertEpochToDate = value => { + const convertedEpoch = parseInt(value) + new Date().getTimezoneOffset() * -1; + return new Date(convertedEpoch); +}; diff --git a/cogboard-webapp/src/components/ZabbixChart/index.js b/cogboard-webapp/src/components/ZabbixChart/index.js index a5d14bf67..5796551c1 100644 --- a/cogboard-webapp/src/components/ZabbixChart/index.js +++ b/cogboard-webapp/src/components/ZabbixChart/index.js @@ -1,134 +1,156 @@ -import React, { useMemo, useState } from 'react'; -import { shallowEqual, useSelector } from 'react-redux'; -import Chartist from 'chartist'; -import ChartistGraph from 'react-chartist'; -import { StyledZabbixChart } from './styled'; -import { getNumberOfElements, calculatePercentageValue, convertEpochToDate } from './helpers'; -import { - COLORS, - ZABBIX_METRICS_WITH_MAX_VALUE, - ZABBIX_METRICS_WITH_PROGRESS -} from '../../constants'; - -const zabbixChartConfig = { - column1: { - numberOfResults: 6, - }, - column2: { - numberOfResults: 15, - }, - column3: { - numberOfResults: 23, - }, - other: { - numberOfResults: 40, - } -} - -const ZabbixChart = ({ id, content }) => { - const [data, setData] = useState({}) - const widgetData = useSelector( - ({ widgets }) => widgets.widgetsById[id], - shallowEqual - ); - const widgetZabbixMetric = widgetData.selectedZabbixMetric; - const widgetConfig = widgetData.config; - const range = widgetData.range || []; - const maxValue = widgetData.maxValue || 0; - const setProgressSize = zabbixChartConfig[`column${widgetConfig.columns}`] - ? zabbixChartConfig[`column${widgetConfig.columns}`] - : zabbixChartConfig.other; - const checkMetricHasMaxValue = ZABBIX_METRICS_WITH_MAX_VALUE.includes(widgetZabbixMetric); - const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes(widgetZabbixMetric); - const options = { - chartPadding: 0, - width: `95%`, - height: '100%' - }; - - useMemo(() => { - const LABELS = Object.keys(content.history).map((label) => convertEpochToDate(label).toLocaleString()); - let SERIES = Object.values(content.history); - - if (checkMetricHasMaxValue) { - SERIES = SERIES.map((serie) => { - return Math.round(serie / Math.pow(10, 9)); - }); - } - - setData({ - labels: getNumberOfElements(LABELS, setProgressSize.numberOfResults), - series: [ - getNumberOfElements(SERIES, setProgressSize.numberOfResults) - ] - }); - }, [content.history, setProgressSize, checkMetricHasMaxValue]); - - const setAxisYTitle = () => { - let titleText; - - if (checkMetricHasMaxValue) { - titleText = '[GB]'; - } else if (!checkMetricHasProgress) { - titleText = 'No.' - } else { - titleText = '[%]' - } - - return titleText; - } - - const setBarColor = (value, maxValue, range) => { - if (!value) return `${COLORS.WHITE}`; - - const percentageValue = checkMetricHasMaxValue ? calculatePercentageValue(value.y, maxValue) : value.y; - let barColorStatus; - - if (percentageValue > range[1]) { - barColorStatus = `${COLORS.RED}`; - } else if (percentageValue < range[0]) { - barColorStatus = `${COLORS.GREEN_DEFAULT}`; - } else { - barColorStatus = `${COLORS.ORANGE}`; - } - - return barColorStatus; - } - - const onDrawHandler = (context) => { - const barColor = setBarColor(context.value, maxValue, range); - - if (context.type === 'label' && context.axis.units.pos === 'x') { - const textHtml = ['

'].join(''); - const multilineText = Chartist.Svg('svg').foreignObject(textHtml, { x: context.x, y: context.y, width: context.axis.stepLength, height: 20 }, 'ct-label ct-horizontal cta-end custom-label', true); - context.element.replace(multilineText); - } - - if (context.type === 'bar' && checkMetricHasProgress) { - context.element.attr({ - style: `stroke: ${barColor};` - }); - } else { - context.element.attr({ - style: `stroke: ${COLORS.WHITE};` - }); - } - } - - return ( - - onDrawHandler(e) - }} - data={data} - options={options} - type='Bar' - /> - - ); -}; - -export default ZabbixChart; +import React, { useMemo, useState } from 'react'; +import { shallowEqual, useSelector } from 'react-redux'; +import Chartist from 'chartist'; +import ChartistGraph from 'react-chartist'; +import { StyledZabbixChart } from './styled'; +import { + getNumberOfElements, + calculatePercentageValue, + convertEpochToDate +} from './helpers'; +import { + COLORS, + ZABBIX_METRICS_WITH_MAX_VALUE, + ZABBIX_METRICS_WITH_PROGRESS +} from '../../constants'; + +const zabbixChartConfig = { + column1: { + numberOfResults: 6 + }, + column2: { + numberOfResults: 15 + }, + column3: { + numberOfResults: 23 + }, + other: { + numberOfResults: 40 + } +}; + +const ZabbixChart = ({ id, content }) => { + const [data, setData] = useState({}); + const widgetData = useSelector( + ({ widgets }) => widgets.widgetsById[id], + shallowEqual + ); + const widgetZabbixMetric = widgetData.selectedZabbixMetric; + const widgetConfig = widgetData.config; + const range = widgetData.range || []; + const maxValue = widgetData.maxValue || 0; + const setProgressSize = zabbixChartConfig[`column${widgetConfig.columns}`] + ? zabbixChartConfig[`column${widgetConfig.columns}`] + : zabbixChartConfig.other; + const checkMetricHasMaxValue = ZABBIX_METRICS_WITH_MAX_VALUE.includes( + widgetZabbixMetric + ); + const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes( + widgetZabbixMetric + ); + const options = { + chartPadding: 0, + width: `95%`, + height: '100%' + }; + + useMemo(() => { + const LABELS = Object.keys(content.history).map(label => + convertEpochToDate(label).toLocaleString() + ); + let SERIES = Object.values(content.history); + + if (checkMetricHasMaxValue) { + SERIES = SERIES.map(serie => { + return Math.round(serie / Math.pow(10, 9)); + }); + } + + setData({ + labels: getNumberOfElements(LABELS, setProgressSize.numberOfResults), + series: [getNumberOfElements(SERIES, setProgressSize.numberOfResults)] + }); + }, [content.history, setProgressSize, checkMetricHasMaxValue]); + + const setAxisYTitle = () => { + let titleText; + + if (checkMetricHasMaxValue) { + titleText = '[GB]'; + } else if (!checkMetricHasProgress) { + titleText = 'No.'; + } else { + titleText = '[%]'; + } + + return titleText; + }; + + const setBarColor = (value, maxValue, range) => { + if (!value) return `${COLORS.WHITE}`; + + const percentageValue = checkMetricHasMaxValue + ? calculatePercentageValue(value.y, maxValue) + : value.y; + let barColorStatus; + + if (percentageValue > range[1]) { + barColorStatus = `${COLORS.RED}`; + } else if (percentageValue < range[0]) { + barColorStatus = `${COLORS.GREEN_DEFAULT}`; + } else { + barColorStatus = `${COLORS.ORANGE}`; + } + + return barColorStatus; + }; + + const onDrawHandler = context => { + const barColor = setBarColor(context.value, maxValue, range); + + if (context.type === 'label' && context.axis.units.pos === 'x') { + const textHtml = [ + '
' + ].join(''); + const multilineText = Chartist.Svg('svg').foreignObject( + textHtml, + { + x: context.x, + y: context.y, + width: context.axis.stepLength, + height: 20 + }, + 'ct-label ct-horizontal cta-end custom-label', + true + ); + context.element.replace(multilineText); + } + + if (context.type === 'bar' && checkMetricHasProgress) { + context.element.attr({ + style: `stroke: ${barColor};` + }); + } else { + context.element.attr({ + style: `stroke: ${COLORS.WHITE};` + }); + } + }; + + return ( + + onDrawHandler(e) + }} + data={data} + options={options} + type="Bar" + /> + + ); +}; + +export default ZabbixChart; diff --git a/cogboard-webapp/src/components/ZabbixChart/styled.js b/cogboard-webapp/src/components/ZabbixChart/styled.js index 2b824d26c..525bd4441 100644 --- a/cogboard-webapp/src/components/ZabbixChart/styled.js +++ b/cogboard-webapp/src/components/ZabbixChart/styled.js @@ -1,70 +1,70 @@ -import styled from '@emotion/styled/macro'; - -import { COLORS } from '../../constants'; - -export const StyledZabbixChart = styled.div` - position: relative; - - &::before, - &::after { - color: ${COLORS.WHITE}; - font-size: 11px; - position: absolute; - text-align: center; - } - - &::before { - content: attr(data-axis-x); - width: 100%; - left: 0; - bottom: 2px; - } - - &::after { - content: attr(data-axis-y); - left: -18px; - top: 36%; - transform: rotate(-90deg) translateY(50%); - } - - svg { - overflow: visible; - } - - .ct-label { - color: ${COLORS.WHITE}; - } - - .custom-label { - overflow: visible; - text-align: center; - - .tooltip { - background-color: white; - border-radius: 50%; - display: inline-block; - height: 6px; - margin: 0 auto; - position: relative; - width: 6px; - - &::after { - background-color: rgba(97, 97, 97, 0.9); - color: ${COLORS.WHITE}; - content: attr(data-tooltip); - left: -32px; - opacity: 0; - padding: 4px 8px; - position: absolute; - text-align: center; - top: 16px; - visibility: hidden; - } - } - - &:hover .tooltip::after { - opacity: 1; - visibility: visible; - } - } -`; +import styled from '@emotion/styled/macro'; + +import { COLORS } from '../../constants'; + +export const StyledZabbixChart = styled.div` + position: relative; + + &::before, + &::after { + color: ${COLORS.WHITE}; + font-size: 11px; + position: absolute; + text-align: center; + } + + &::before { + content: attr(data-axis-x); + width: 100%; + left: 0; + bottom: 2px; + } + + &::after { + content: attr(data-axis-y); + left: -18px; + top: 36%; + transform: rotate(-90deg) translateY(50%); + } + + svg { + overflow: visible; + } + + .ct-label { + color: ${COLORS.WHITE}; + } + + .custom-label { + overflow: visible; + text-align: center; + + .tooltip { + background-color: white; + border-radius: 50%; + display: inline-block; + height: 6px; + margin: 0 auto; + position: relative; + width: 6px; + + &::after { + background-color: rgba(97, 97, 97, 0.9); + color: ${COLORS.WHITE}; + content: attr(data-tooltip); + left: -32px; + opacity: 0; + padding: 4px 8px; + position: absolute; + text-align: center; + top: 16px; + visibility: hidden; + } + } + + &:hover .tooltip::after { + opacity: 1; + visibility: visible; + } + } +`; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/LinkListInput.js b/cogboard-webapp/src/components/widgets/dialogFields/LinkListInput.js index 65f806730..8c5d44475 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/LinkListInput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/LinkListInput.js @@ -1,177 +1,177 @@ -import React, { useState } from 'react'; -import { remove } from 'ramda'; -import { v4 } from 'uuid'; -import { prepareChangeEvent, RenderDragableList } from './helpers'; -import { hasError } from '../../../utils/components'; -import { Add, Check, Error } from '@material-ui/icons'; -import { StyledFab, StyledInput, StyledFormControl } from './styled'; -import { StyledFormHelperText } from '../../styled'; - -const LinkListInput = ({ value, onChange }) => { - const [formValueTitle, setFormValueTitle] = useState(''); - const [formValueUrl, setFormValueUrl] = useState(''); - const [formError, setFormError] = useState(); - const [urlError, setUrlError] = useState(false); - const [editMode, setEditMode] = useState(false); - const handleChangeTitle = event => setFormValueTitle(event.target.value); - const handleChangeUrl = event => { - if (!event.target.value.match(/^(http|https|ws|ftp):\/\/.*([:.]).*/)) { - setUrlError(true); - } else { - setUrlError(false); - } - - setFormValueUrl(event.target.value); - }; - - const [linkList, setLinkList] = useState(() => - (value || []).map(linkItem => { - return { - id: linkItem.id, - linkTitle: linkItem.linkTitle, - linkUrl: linkItem.linkUrl - }; - }) - ); - - const resetInput = () => { - setFormValueTitle(''); - setFormValueUrl(''); - }; - - const onSaveClick = () => { - handleSave({ - linkTitle: formValueTitle, - linkUrl: formValueUrl - }); - }; - - const handleSave = linkItem => { - let updatedItems; - - if (urlError) { - return; - } else if ( - linkItem.linkUrl.length === 0 || - linkItem.linkTitle.length === 0 - ) { - setFormError('Fill Title and Url field'); - return; - } else { - setFormError(undefined); - } - - if (editMode) { - updatedItems = linkList; - const updatedItemId = linkList.findIndex(el => el.id === editMode); - updatedItems[updatedItemId] = { - id: updatedItems[updatedItemId].id, - linkTitle: linkItem.linkTitle, - linkUrl: linkItem.linkUrl - }; - setEditMode(false); - } else { - updatedItems = [ - ...linkList, - { id: v4(), linkTitle: linkItem.linkTitle, linkUrl: linkItem.linkUrl } - ]; - } - - setLinkList(updatedItems); - onChange(prepareChangeEvent(updatedItems, 'array')); - resetInput(); - }; - - const handleDelete = itemIndex => { - const itemList = remove(itemIndex, 1, linkList); - setLinkList(itemList); - onChange(prepareChangeEvent(itemList, 'array')); - }; - - const handleKeyPressed = event => { - if (event.key === 'Enter') { - event.preventDefault(); - - if (!formValueUrl) { - return; - } - - handleSave({ - id: v4(), - linkUrl: formValueUrl, - linkTitle: formValueTitle - }); - } - - return; - }; - - const handleEdit = id => { - const editItem = linkList.find(el => el.id === id); - setFormValueTitle(editItem.linkTitle); - setFormValueUrl(editItem.linkUrl); - setEditMode(editItem.id); - }; - - return ( - - {formError && ( - - - {formError} - - )} - - - {urlError && ( - - - Invalid Url - - )} - - {editMode ? ( - <> - Save Link - - ) : ( - <> - Add Link - - )} - - - - ); -}; - -export default LinkListInput; +import React, { useState } from 'react'; +import { remove } from 'ramda'; +import { v4 } from 'uuid'; +import { prepareChangeEvent, RenderDragableList } from './helpers'; +import { hasError } from '../../../utils/components'; +import { Add, Check, Error } from '@material-ui/icons'; +import { StyledFab, StyledInput, StyledFormControl } from './styled'; +import { StyledFormHelperText } from '../../styled'; + +const LinkListInput = ({ value, onChange }) => { + const [formValueTitle, setFormValueTitle] = useState(''); + const [formValueUrl, setFormValueUrl] = useState(''); + const [formError, setFormError] = useState(); + const [urlError, setUrlError] = useState(false); + const [editMode, setEditMode] = useState(false); + const handleChangeTitle = event => setFormValueTitle(event.target.value); + const handleChangeUrl = event => { + if (!event.target.value.match(/^(http|https|ws|ftp):\/\/.*([:.]).*/)) { + setUrlError(true); + } else { + setUrlError(false); + } + + setFormValueUrl(event.target.value); + }; + + const [linkList, setLinkList] = useState(() => + (value || []).map(linkItem => { + return { + id: linkItem.id, + linkTitle: linkItem.linkTitle, + linkUrl: linkItem.linkUrl + }; + }) + ); + + const resetInput = () => { + setFormValueTitle(''); + setFormValueUrl(''); + }; + + const onSaveClick = () => { + handleSave({ + linkTitle: formValueTitle, + linkUrl: formValueUrl + }); + }; + + const handleSave = linkItem => { + let updatedItems; + + if (urlError) { + return; + } else if ( + linkItem.linkUrl.length === 0 || + linkItem.linkTitle.length === 0 + ) { + setFormError('Fill Title and Url field'); + return; + } else { + setFormError(undefined); + } + + if (editMode) { + updatedItems = linkList; + const updatedItemId = linkList.findIndex(el => el.id === editMode); + updatedItems[updatedItemId] = { + id: updatedItems[updatedItemId].id, + linkTitle: linkItem.linkTitle, + linkUrl: linkItem.linkUrl + }; + setEditMode(false); + } else { + updatedItems = [ + ...linkList, + { id: v4(), linkTitle: linkItem.linkTitle, linkUrl: linkItem.linkUrl } + ]; + } + + setLinkList(updatedItems); + onChange(prepareChangeEvent(updatedItems, 'array')); + resetInput(); + }; + + const handleDelete = itemIndex => { + const itemList = remove(itemIndex, 1, linkList); + setLinkList(itemList); + onChange(prepareChangeEvent(itemList, 'array')); + }; + + const handleKeyPressed = event => { + if (event.key === 'Enter') { + event.preventDefault(); + + if (!formValueUrl) { + return; + } + + handleSave({ + id: v4(), + linkUrl: formValueUrl, + linkTitle: formValueTitle + }); + } + + return; + }; + + const handleEdit = id => { + const editItem = linkList.find(el => el.id === id); + setFormValueTitle(editItem.linkTitle); + setFormValueUrl(editItem.linkUrl); + setEditMode(editItem.id); + }; + + return ( + + {formError && ( + + + {formError} + + )} + + + {urlError && ( + + + Invalid Url + + )} + + {editMode ? ( + <> + Save Link + + ) : ( + <> + Add Link + + )} + + + + ); +}; + +export default LinkListInput; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/MaxValueInput.js b/cogboard-webapp/src/components/widgets/dialogFields/MaxValueInput.js index f933f896d..a70ef778c 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/MaxValueInput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/MaxValueInput.js @@ -1,38 +1,54 @@ -import React from 'react'; -import { TextField } from '@material-ui/core'; -import { ZABBIX_METRICS_WITH_PROGRESS, ZABBIX_METRICS_WITH_MAX_VALUE } from '../../../constants'; -import { prepareChangeEvent } from './helpers'; - -const MaxValueInput = ({ error, values, label, dataCy, onChange, ...other }) => { - const selectedMetric = values.selectedZabbixMetric; - - const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes(selectedMetric); - const checkMetricHasMaxValue = ZABBIX_METRICS_WITH_MAX_VALUE.includes(selectedMetric); - - const handleChange = (evt) => { - const formattedValue = evt.target.value ? parseFloat(evt.target.value.replace(/,/g, '')) : '0'; - onChange(prepareChangeEvent(parseInt(formattedValue), 'number')); - } - - return ( - <> - {(checkMetricHasProgress && checkMetricHasMaxValue) && ( - - )} - - ); -}; - -export default MaxValueInput; +import React from 'react'; +import { TextField } from '@material-ui/core'; +import { + ZABBIX_METRICS_WITH_PROGRESS, + ZABBIX_METRICS_WITH_MAX_VALUE +} from '../../../constants'; +import { prepareChangeEvent } from './helpers'; + +const MaxValueInput = ({ + error, + values, + label, + dataCy, + onChange, + ...other +}) => { + const selectedMetric = values.selectedZabbixMetric; + + const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes( + selectedMetric + ); + const checkMetricHasMaxValue = ZABBIX_METRICS_WITH_MAX_VALUE.includes( + selectedMetric + ); + + const handleChange = evt => { + const formattedValue = evt.target.value + ? parseFloat(evt.target.value.replace(/,/g, '')) + : '0'; + onChange(prepareChangeEvent(parseInt(formattedValue), 'number')); + }; + + return ( + <> + {checkMetricHasProgress && checkMetricHasMaxValue && ( + + )} + + ); +}; + +export default MaxValueInput; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/RangeSlider.js b/cogboard-webapp/src/components/widgets/dialogFields/RangeSlider.js index 6b7d43426..5fc0a32a4 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/RangeSlider.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/RangeSlider.js @@ -1,52 +1,54 @@ -import React, { useState } from 'react'; -import Slider from '@material-ui/core/Slider'; -import { Typography } from '@material-ui/core'; -import { prepareChangeEvent } from './helpers'; -import { StyledRangeSliderForm } from './styled'; -import { ZABBIX_METRICS_WITH_PROGRESS } from '../../../constants'; - -const RangeSlider = ({ value, values, onChange }) => { - const widgetZabbixMetric = values.selectedZabbixMetric; - const marks = [ - { - value: 0, - label: '0%' - }, - { - value: 100, - label: '100%' - } - ]; - const [rangeValue, setRangeValue] = useState(value); - - const handleChange = (_, newValue) => setRangeValue(newValue); - const handleChangeCommited = (_, newValue) => { - onChange(prepareChangeEvent(newValue, 'array')); - }; - - const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes(widgetZabbixMetric); - const setAriaAttributeText = value => `${value}%`; - - return ( - <> - {checkMetricHasProgress && ( - - Range (%) - - - )} - - ); -}; - -export default RangeSlider; +import React, { useState } from 'react'; +import Slider from '@material-ui/core/Slider'; +import { Typography } from '@material-ui/core'; +import { prepareChangeEvent } from './helpers'; +import { StyledRangeSliderForm } from './styled'; +import { ZABBIX_METRICS_WITH_PROGRESS } from '../../../constants'; + +const RangeSlider = ({ value, values, onChange }) => { + const widgetZabbixMetric = values.selectedZabbixMetric; + const marks = [ + { + value: 0, + label: '0%' + }, + { + value: 100, + label: '100%' + } + ]; + const [rangeValue, setRangeValue] = useState(value); + + const handleChange = (_, newValue) => setRangeValue(newValue); + const handleChangeCommited = (_, newValue) => { + onChange(prepareChangeEvent(newValue, 'array')); + }; + + const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes( + widgetZabbixMetric + ); + const setAriaAttributeText = value => `${value}%`; + + return ( + <> + {checkMetricHasProgress && ( + + Range (%) + + + )} + + ); +}; + +export default RangeSlider; diff --git a/cogboard-webapp/src/components/widgets/dialogFields/ToDoListinput.js b/cogboard-webapp/src/components/widgets/dialogFields/ToDoListinput.js index 79f57e09f..ba5983490 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/ToDoListinput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/ToDoListinput.js @@ -1,179 +1,179 @@ -import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { v4 } from 'uuid'; -import { remove } from 'ramda'; -import { postWidgetContentUpdate } from '../../../utils/fetch'; -import { saveWidget } from '../../../actions/thunks'; -import { prepareChangeEvent, RenderDragableList } from './helpers'; -import { FormControl } from '@material-ui/core'; -import { Add, Check, Delete } from '@material-ui/icons'; -import { StyledFab, StyledInput, StyledFabGroup } from './styled'; - -const ToDoListInput = ({ value, values, onChange }) => { - const [formValueItemText, setFormValueItemText] = useState(''); - const [editMode, setEditMode] = useState(false); - const dispatch = useDispatch(); - const content = values.content || {}; - const handleChangeValItemText = event => - setFormValueItemText(event.target.value); - const selectedItems = content.selectedItems || []; - const widgetId = values.id || ''; - const [items, setItems] = useState(() => - (value || []).map(item => { - return { - id: item.id, - itemText: item.itemText - }; - }) - ); - - const resetInput = () => { - setFormValueItemText(''); - }; - - const onSaveClick = () => { - handleSave({ - itemText: formValueItemText - }); - }; - - const handleSave = item => { - let updatedItems; - - if (item.itemText.length === 0) { - return; - } - - if (editMode) { - updatedItems = items; - const updatedItemId = items.findIndex(el => el.id === editMode); - updatedItems[updatedItemId] = { - id: updatedItems[updatedItemId].id, - itemText: item.itemText - }; - setEditMode(false); - } else { - updatedItems = [ - ...items, - { - id: `item-${v4()}`, - itemText: item.itemText - } - ]; - } - - setItems(updatedItems); - onChange(prepareChangeEvent(updatedItems, 'array')); - resetInput(); - }; - - const onInputKeyDown = event => { - if (event.key === 'Enter') { - event.preventDefault(); - onSaveClick(); - } - }; - - const onClearClick = () => { - if (!selectedItems) return; - - const itemsToClear = new Set(selectedItems); - const filteredArray = items.filter(obj => !itemsToClear.has(obj.id)); - setItems(filteredArray); - onChange(prepareChangeEvent(filteredArray, 'array')); - - postWidgetContentUpdate({ - id: widgetId, - clearItems: true - }); - dispatch( - saveWidget({ - widgetId, - values: { ...values, toDoListItems: filteredArray } - }) - ); - }; - - const handleEdit = id => { - const editItem = items.find(el => el.id === id); - setFormValueItemText(editItem.itemText); - setEditMode(editItem.id); - }; - - const handleDelete = itemIndex => { - let itemList = remove(itemIndex, 1, items); - const itemId = items[itemIndex].id; - - setItems(itemList); - onChange(prepareChangeEvent(itemList, 'array')); - - if (selectedItems.includes(itemId)) { - postWidgetContentUpdate({ - id: widgetId, - selectedItem: itemId - }); - dispatch( - saveWidget({ widgetId, values: { ...values, toDoListItems: itemList } }) - ); - } - }; - - return ( - - - - - {editMode ? ( - <> - Save item - - ) : ( - <> - Add Item - - )} - - {selectedItems.length > 0 && ( - - <> - Clear Selected - - - )} - - - - ); -}; - -export default ToDoListInput; +import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { v4 } from 'uuid'; +import { remove } from 'ramda'; +import { postWidgetContentUpdate } from '../../../utils/fetch'; +import { saveWidget } from '../../../actions/thunks'; +import { prepareChangeEvent, RenderDragableList } from './helpers'; +import { FormControl } from '@material-ui/core'; +import { Add, Check, Delete } from '@material-ui/icons'; +import { StyledFab, StyledInput, StyledFabGroup } from './styled'; + +const ToDoListInput = ({ value, values, onChange }) => { + const [formValueItemText, setFormValueItemText] = useState(''); + const [editMode, setEditMode] = useState(false); + const dispatch = useDispatch(); + const content = values.content || {}; + const handleChangeValItemText = event => + setFormValueItemText(event.target.value); + const selectedItems = content.selectedItems || []; + const widgetId = values.id || ''; + const [items, setItems] = useState(() => + (value || []).map(item => { + return { + id: item.id, + itemText: item.itemText + }; + }) + ); + + const resetInput = () => { + setFormValueItemText(''); + }; + + const onSaveClick = () => { + handleSave({ + itemText: formValueItemText + }); + }; + + const handleSave = item => { + let updatedItems; + + if (item.itemText.length === 0) { + return; + } + + if (editMode) { + updatedItems = items; + const updatedItemId = items.findIndex(el => el.id === editMode); + updatedItems[updatedItemId] = { + id: updatedItems[updatedItemId].id, + itemText: item.itemText + }; + setEditMode(false); + } else { + updatedItems = [ + ...items, + { + id: `item-${v4()}`, + itemText: item.itemText + } + ]; + } + + setItems(updatedItems); + onChange(prepareChangeEvent(updatedItems, 'array')); + resetInput(); + }; + + const onInputKeyDown = event => { + if (event.key === 'Enter') { + event.preventDefault(); + onSaveClick(); + } + }; + + const onClearClick = () => { + if (!selectedItems) return; + + const itemsToClear = new Set(selectedItems); + const filteredArray = items.filter(obj => !itemsToClear.has(obj.id)); + setItems(filteredArray); + onChange(prepareChangeEvent(filteredArray, 'array')); + + postWidgetContentUpdate({ + id: widgetId, + clearItems: true + }); + dispatch( + saveWidget({ + widgetId, + values: { ...values, toDoListItems: filteredArray } + }) + ); + }; + + const handleEdit = id => { + const editItem = items.find(el => el.id === id); + setFormValueItemText(editItem.itemText); + setEditMode(editItem.id); + }; + + const handleDelete = itemIndex => { + let itemList = remove(itemIndex, 1, items); + const itemId = items[itemIndex].id; + + setItems(itemList); + onChange(prepareChangeEvent(itemList, 'array')); + + if (selectedItems.includes(itemId)) { + postWidgetContentUpdate({ + id: widgetId, + selectedItem: itemId + }); + dispatch( + saveWidget({ widgetId, values: { ...values, toDoListItems: itemList } }) + ); + } + }; + + return ( + + + + + {editMode ? ( + <> + Save item + + ) : ( + <> + Add Item + + )} + + {selectedItems.length > 0 && ( + + <> + Clear Selected + + + )} + + + + ); +}; + +export default ToDoListInput; diff --git a/cogboard-webapp/src/components/widgets/types/LinkListWidget/index.js b/cogboard-webapp/src/components/widgets/types/LinkListWidget/index.js index dd9a4a49e..95ea23a81 100644 --- a/cogboard-webapp/src/components/widgets/types/LinkListWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LinkListWidget/index.js @@ -1,44 +1,44 @@ -import React from 'react'; -import { array } from 'prop-types'; -import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; -import { Link } from '@material-ui/core'; -import { StyledList, StyledListItem } from './styled'; -import { StyledNoItemsInfo } from '../../../Widget/styled'; - -const LinkListWidget = ({ linkListItems }) => { - return ( - <> - {linkListItems.length > 0 ? ( - - {linkListItems.map((item, id) => ( - - - {item.linkTitle} - - - ))} - - ) : ( - - -

Links List Empty

-
- )} - - ); -}; - -LinkListWidget.propTypes = { - linkListItems: array -}; - -LinkListWidget.defaultProps = { - linkListItems: [] -}; - -export default LinkListWidget; +import React from 'react'; +import { array } from 'prop-types'; +import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; +import { Link } from '@material-ui/core'; +import { StyledList, StyledListItem } from './styled'; +import { StyledNoItemsInfo } from '../../../Widget/styled'; + +const LinkListWidget = ({ linkListItems }) => { + return ( + <> + {linkListItems.length > 0 ? ( + + {linkListItems.map((item, id) => ( + + + {item.linkTitle} + + + ))} + + ) : ( + + +

Links List Empty

+
+ )} + + ); +}; + +LinkListWidget.propTypes = { + linkListItems: array +}; + +LinkListWidget.defaultProps = { + linkListItems: [] +}; + +export default LinkListWidget; diff --git a/cogboard-webapp/src/components/widgets/types/LinkListWidget/styled.js b/cogboard-webapp/src/components/widgets/types/LinkListWidget/styled.js index e80d4aa36..7498099fc 100644 --- a/cogboard-webapp/src/components/widgets/types/LinkListWidget/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LinkListWidget/styled.js @@ -1,13 +1,13 @@ -import styled from '@emotion/styled/macro'; - -import { List, ListItem } from '@material-ui/core'; - -export const StyledListItem = styled(ListItem)` - a:hover { - text-deecoration: underline; - } -`; - -export const StyledList = styled(List)` - padding: 0; -`; +import styled from '@emotion/styled/macro'; + +import { List, ListItem } from '@material-ui/core'; + +export const StyledListItem = styled(ListItem)` + a:hover { + text-deecoration: underline; + } +`; + +export const StyledList = styled(List)` + padding: 0; +`; diff --git a/cogboard-webapp/src/components/widgets/types/TextWidget/index.js b/cogboard-webapp/src/components/widgets/types/TextWidget/index.js index 53b229b78..032e2d52b 100644 --- a/cogboard-webapp/src/components/widgets/types/TextWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/TextWidget/index.js @@ -1,86 +1,86 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useSize } from 'react-hook-size'; -import { bool, string } from 'prop-types'; - -import { - TypographyVariant, - CenterWrapper, - StyledPre, - RotatedStyledPre, - OverflowingText, - SingleLineText, - SetWidth -} from './styled'; - -export const ModifiedWidth = (component, height) => { - if (height) { - return SetWidth(component, height); - } - - return component; -}; - -const TruncatedText = ({ - isVertical, - parentDimensions, - children, - singleLine -}) => { - let TruncatedPre = null; - - if (isVertical && parentDimensions !== null) { - const { height } = parentDimensions; - const ModifiedPre = ModifiedWidth(RotatedStyledPre, height); - const VerticalText = height ? ModifiedPre : RotatedStyledPre; - - TruncatedPre = OverflowingText(VerticalText); - } else if (singleLine) { - TruncatedPre = SingleLineText(StyledPre); - } else { - TruncatedPre = OverflowingText(StyledPre); - } - - return {children}; -}; - -const TextWidget = ({ text, textSize, isVertical, singleLine }) => { - const targetRef = useRef(null); - const centerWrapperDimensions = useSize(targetRef); - const [dimensions, setDimensions] = useState(null); - - useEffect(() => { - if (centerWrapperDimensions.height) { - setDimensions(centerWrapperDimensions); - } - }, [centerWrapperDimensions]); - - return ( - - - - {text} - - - - ); -}; - -TextWidget.propTypes = { - text: string, - textSize: string, - isVertical: bool, - singleLine: bool -}; - -TextWidget.defaultProps = { - text: '', - textSize: '', - isVertical: false, - singleLine: false -}; - -export default TextWidget; +import React, { useState, useRef, useEffect } from 'react'; +import { useSize } from 'react-hook-size'; +import { bool, string } from 'prop-types'; + +import { + TypographyVariant, + CenterWrapper, + StyledPre, + RotatedStyledPre, + OverflowingText, + SingleLineText, + SetWidth +} from './styled'; + +export const ModifiedWidth = (component, height) => { + if (height) { + return SetWidth(component, height); + } + + return component; +}; + +const TruncatedText = ({ + isVertical, + parentDimensions, + children, + singleLine +}) => { + let TruncatedPre = null; + + if (isVertical && parentDimensions !== null) { + const { height } = parentDimensions; + const ModifiedPre = ModifiedWidth(RotatedStyledPre, height); + const VerticalText = height ? ModifiedPre : RotatedStyledPre; + + TruncatedPre = OverflowingText(VerticalText); + } else if (singleLine) { + TruncatedPre = SingleLineText(StyledPre); + } else { + TruncatedPre = OverflowingText(StyledPre); + } + + return {children}; +}; + +const TextWidget = ({ text, textSize, isVertical, singleLine }) => { + const targetRef = useRef(null); + const centerWrapperDimensions = useSize(targetRef); + const [dimensions, setDimensions] = useState(null); + + useEffect(() => { + if (centerWrapperDimensions.height) { + setDimensions(centerWrapperDimensions); + } + }, [centerWrapperDimensions]); + + return ( + + + + {text} + + + + ); +}; + +TextWidget.propTypes = { + text: string, + textSize: string, + isVertical: bool, + singleLine: bool +}; + +TextWidget.defaultProps = { + text: '', + textSize: '', + isVertical: false, + singleLine: false +}; + +export default TextWidget; diff --git a/cogboard-webapp/src/components/widgets/types/ZabbixWidget/index.js b/cogboard-webapp/src/components/widgets/types/ZabbixWidget/index.js index 106b2a7a7..57f6b0428 100644 --- a/cogboard-webapp/src/components/widgets/types/ZabbixWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/ZabbixWidget/index.js @@ -1,123 +1,129 @@ -import React from 'react'; -import { shallowEqual, useSelector } from 'react-redux'; -import SemiCircleProgress from '../../../SemiProgressBar'; -import { - StyledArrowDown, - StyledArrowUp, - StyledMetricName, - StyledNumericValue, - StyledZabbixWrapper, - StyledNumericValueWithIcon -} from './styled'; -import { - COLORS, - ZABBIX_METRICS, - ZABBIX_METRICS_WITH_MAX_VALUE, - ZABBIX_METRICS_WITH_PROGRESS -} from '../../../../constants'; - - -const progressBarWidth = { - column1: { - diameter: 150 - }, - column2: { - diameter: 200 - }, - other: { - diameter: 220 - } -}; - -const ZabbixWidget = ({ id, lastvalue, history }) => { - const widgetData = useSelector( - ({ widgets }) => widgets.widgetsById[id], - shallowEqual - ); - const upTimeMetricName = 'system.uptime'; - const widgetConfig = widgetData.config; - const widgetZabbixMetric = widgetData.selectedZabbixMetric; - const maxValue = widgetData.maxValue; - - const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes(widgetZabbixMetric); - const checkMetricHasMaxValue = ZABBIX_METRICS_WITH_MAX_VALUE.includes(widgetZabbixMetric); - - const setProgressSize = () => { - const widgetColumns = widgetConfig.columns; - return progressBarWidth[`column${widgetColumns}`] - ? progressBarWidth[`column${widgetColumns}`].diameter - : progressBarWidth.other.diameter; - }; - - const calculatePercentageValue = () => { - if (!lastvalue) return 0; - if (!checkMetricHasMaxValue) return parseInt(lastvalue, 10); - - return Math.round((100 * lastvalue) / (maxValue * Math.pow(10, 9))); - }; - - const convertMetricTitle = () => { - if (!widgetZabbixMetric) return ''; - - return ZABBIX_METRICS.find(item => item.value === widgetZabbixMetric).display; - }; - - const convertToGigaBytes = () => { - if (!lastvalue) return 0; - return Math.round(lastvalue / Math.pow(10, 9)); - }; - - const secondsToTime = value => { - const days = Math.floor(value / 3600 / 24).toString(), - hours = Math.floor((value / 3600) % 24).toString(), - minutes = Math.floor((value % 3600) / 60).toString(); - - return days + 'd:' + hours + 'h:' + minutes + 'm'; - }; - - const renderNoProgressContent = () => { - if (!lastvalue) return; - - const historyValues = Object.values(history); - const historyCurrentValue = historyValues[historyValues.length - 1]; - const historyPrevValue = historyValues[historyValues.length - 2]; - const value = - widgetZabbixMetric === upTimeMetricName - ? secondsToTime(lastvalue) - : parseInt(lastvalue, 10); - - return ( - <> - { - widgetZabbixMetric === upTimeMetricName ? ( - {value} - ) : ( - - {value} - { historyCurrentValue > historyPrevValue ? : } - - ) - } - - ); - }; - - return ( - - {checkMetricHasProgress ? ( - - ) : ( - renderNoProgressContent() - )} - {convertMetricTitle()} - - ); -}; - -export default ZabbixWidget; +import React from 'react'; +import { shallowEqual, useSelector } from 'react-redux'; +import SemiCircleProgress from '../../../SemiProgressBar'; +import { + StyledArrowDown, + StyledArrowUp, + StyledMetricName, + StyledNumericValue, + StyledZabbixWrapper, + StyledNumericValueWithIcon +} from './styled'; +import { + COLORS, + ZABBIX_METRICS, + ZABBIX_METRICS_WITH_MAX_VALUE, + ZABBIX_METRICS_WITH_PROGRESS +} from '../../../../constants'; + +const progressBarWidth = { + column1: { + diameter: 150 + }, + column2: { + diameter: 200 + }, + other: { + diameter: 220 + } +}; + +const ZabbixWidget = ({ id, lastvalue, history }) => { + const widgetData = useSelector( + ({ widgets }) => widgets.widgetsById[id], + shallowEqual + ); + const upTimeMetricName = 'system.uptime'; + const widgetConfig = widgetData.config; + const widgetZabbixMetric = widgetData.selectedZabbixMetric; + const maxValue = widgetData.maxValue; + + const checkMetricHasProgress = ZABBIX_METRICS_WITH_PROGRESS.includes( + widgetZabbixMetric + ); + const checkMetricHasMaxValue = ZABBIX_METRICS_WITH_MAX_VALUE.includes( + widgetZabbixMetric + ); + + const setProgressSize = () => { + const widgetColumns = widgetConfig.columns; + return progressBarWidth[`column${widgetColumns}`] + ? progressBarWidth[`column${widgetColumns}`].diameter + : progressBarWidth.other.diameter; + }; + + const calculatePercentageValue = () => { + if (!lastvalue) return 0; + if (!checkMetricHasMaxValue) return parseInt(lastvalue, 10); + + return Math.round((100 * lastvalue) / (maxValue * Math.pow(10, 9))); + }; + + const convertMetricTitle = () => { + if (!widgetZabbixMetric) return ''; + + return ZABBIX_METRICS.find(item => item.value === widgetZabbixMetric) + .display; + }; + + const convertToGigaBytes = () => { + if (!lastvalue) return 0; + return Math.round(lastvalue / Math.pow(10, 9)); + }; + + const secondsToTime = value => { + const days = Math.floor(value / 3600 / 24).toString(), + hours = Math.floor((value / 3600) % 24).toString(), + minutes = Math.floor((value % 3600) / 60).toString(); + + return days + 'd:' + hours + 'h:' + minutes + 'm'; + }; + + const renderNoProgressContent = () => { + if (!lastvalue) return; + + const historyValues = Object.values(history); + const historyCurrentValue = historyValues[historyValues.length - 1]; + const historyPrevValue = historyValues[historyValues.length - 2]; + const value = + widgetZabbixMetric === upTimeMetricName + ? secondsToTime(lastvalue) + : parseInt(lastvalue, 10); + + return ( + <> + {widgetZabbixMetric === upTimeMetricName ? ( + {value} + ) : ( + + {value} + {historyCurrentValue > historyPrevValue ? ( + + ) : ( + + )} + + )} + + ); + }; + + return ( + + {checkMetricHasProgress ? ( + + ) : ( + renderNoProgressContent() + )} + {convertMetricTitle()} + + ); +}; + +export default ZabbixWidget; diff --git a/cogboard-webapp/src/components/widgets/types/ZabbixWidget/styled.js b/cogboard-webapp/src/components/widgets/types/ZabbixWidget/styled.js index d0365c42c..35333ecf9 100644 --- a/cogboard-webapp/src/components/widgets/types/ZabbixWidget/styled.js +++ b/cogboard-webapp/src/components/widgets/types/ZabbixWidget/styled.js @@ -1,40 +1,40 @@ -import styled from '@emotion/styled/macro'; - -import { Typography } from '@material-ui/core'; -import { ArrowDownward, ArrowUpward } from '@material-ui/icons'; -import { COLORS } from '../../../../constants'; - -export const StyledArrowDown = styled(ArrowDownward)` - color: ${COLORS.RED}; -`; - -export const StyledArrowUp = styled(ArrowUpward)` - color: ${COLORS.GREEN_DEFAULT};; -`; - -export const StyledMetricName = styled(Typography)` - font-size: 0.775rem; - font-weight: 600; - text-align: center; -`; - -export const StyledNumericValue = styled(Typography)` - margin-bottom: 42px; - text-align: center; -`; - -export const StyledNumericValueWithIcon = styled.div` - align-items: center; - display: flex; - font-size: 16px; - justify-content: center; - margin-bottom: 42px; -`; - -export const StyledZabbixWrapper = styled.div` - display: flex; - flex-direction: column; - flex: 1; - justify-content: flex-end; - margin-bottom: 12px; -`; +import styled from '@emotion/styled/macro'; + +import { Typography } from '@material-ui/core'; +import { ArrowDownward, ArrowUpward } from '@material-ui/icons'; +import { COLORS } from '../../../../constants'; + +export const StyledArrowDown = styled(ArrowDownward)` + color: ${COLORS.RED}; +`; + +export const StyledArrowUp = styled(ArrowUpward)` + color: ${COLORS.GREEN_DEFAULT}; +`; + +export const StyledMetricName = styled(Typography)` + font-size: 0.775rem; + font-weight: 600; + text-align: center; +`; + +export const StyledNumericValue = styled(Typography)` + margin-bottom: 42px; + text-align: center; +`; + +export const StyledNumericValueWithIcon = styled.div` + align-items: center; + display: flex; + font-size: 16px; + justify-content: center; + margin-bottom: 42px; +`; + +export const StyledZabbixWrapper = styled.div` + display: flex; + flex-direction: column; + flex: 1; + justify-content: flex-end; + margin-bottom: 12px; +`; diff --git a/functional/cypress-tests/README.md b/functional/cypress-tests/README.md index b7412c431..5ea559e85 100644 --- a/functional/cypress-tests/README.md +++ b/functional/cypress-tests/README.md @@ -1,103 +1,103 @@ -# Cypress Automated Functional Tests - -## :construction: Work in progress :construction: - -Tests in this part of the repository are testing UI of the CogBoard. Get to know helper functions available right now (and update them if needed). - -## Running tests - -### CLI - -Quick use (i.e. before push): - -`./gradlew functionalTests` - will launch all specs, with local env config file. - -To override env add `-DcypressEnv=envName` system property. Which will run cypress config named `envName.json` under `functional/cypress-tests/cypress/config` directory. You must make sure that you created such a config in a first place. - -To customize config and specs to be launched, you have to run: - -`npx cypress run [--config-file path/to/config.json]`1 `[--spec path/to/spec.js]`2 from the `functional/cypress-tests` directory - -1 - provide custom config file path, by default it will use `functional/cypress-tests/cypress/config/local.json`. - -2 - provide spec file(s) to be launched. By default all specs are run. - -### GUI - -1. Go to `functional/cypress-tests` directory -1. Install Cypress `npm install` (run only once) -1. Open Cypress tools by executing `npx cypress open` - -## Contribution guide - -All cypress related commits should be done on branches starting with `automation/` - -### Tasks to do - -... - -### Coding conventions - -All tests should be written according to [Cypress.io Best Practices guide](https://docs.cypress.io/guides/references/best-practices.html 'Best Practices | Cypress Documentation'). Read it before starting to contribute. - -Additional project specific conventions: - -- For actions that have been already covered in other tests write `cy.request(...)` helper command to bypass UI and speed up the test execution time - - Good example would be the Widget tests. Dashboard creation and dashboard removal steps are performed in each of those tests and take around 2.5s. If we have used API calls instead we would be lookng at `2.5s * numOfWidgets` time reduction. -- Keep test data in separate files. i.e. `../fixtures/Widgets.js` -- No test case dependencies. Each test's expected initial state should be either prepared manually or by `cy.request(...)` call before test starts. -- Actions that impact on other specs (such as saving) should be kept in the same spec. -- Repetitive tests should be written in iterative manner: i.e. `../integration/widgets.js` or FE validation tests in `../integration/dashboards.js` -- ... - -## Available helpers - -Use helpers below to minimize specfiles size. Keep in mind that some of those helpers require certain state of app to work correctly, make sure you learn their code. - -### General - -`cy.saveState()` - Saves current state of the Dashboard - -### User - -`cy.login()` - Log in with credentials specified in configuration file. - _Currently `../cypress.json`_ - -`cy.logout()` - Log out of the application - -`getAuthenticationToken()` - Returns authentication token for username: admin - -`loginWithToken()` - Log in as admin - -### Dashboard - -`cy.openDrawer()` - Open the Dashboard Drawer - -`cy.closeDrawer()` - Close the Dashboard Drawer. - -`cy.chooseDashboard(dashboardName)` - Switch to the Dashboard named `dashboardName`. - -`cy.addDashboard(dashboardName, columnsCount, switchInterval, expectFailure)` - Add Dashboard with a name `dashboardName`, number of columns equal to `columnsCount`, switching interval set to `switchInterval` and `expectFailure` flag which will check if creation dialog has been closed when set to `false` or if creation dialog is still visible when set to `true`. - -`cy.removeDashboard(dashboardNname)` - Remove the Dashboard named `dashboardName`. - -`cy.renewDashboards(username, password)` - API call which generates dashboard specified in `../fixtures/reorderingConfig.json` - -### Widget - -`createWidget(name)` - use any name from `functional/cypress-tests/cypress/fixtures/Widgets.js` for example: `Widgets.whiteSpace.name` - this will add configured widget to current board. Widget type will be deducted from name prop. - -`cy.clickAddWidgetButton()` - Click the Add Widget button visible after login. - -`cy.fillNewWidgetGeneral(widgetType, title, newLine, disabled, columnsCount, rowsCount)` - Fill out the General Tab of the Widget Creation dialog. `widgetType` specifies Widget to be added, `title` is it's name, `newLine` will set the widget to be added on a next row, following the last existing widget on a Dashboard when set to `true`, `disabled` will make the widget disabled when set to `true`. `columnsCount` and `rowsCount` will determine number of, respectively, columns and rows. - -`cy.fillSchedulePeriod(value)` - Fill out the Schedule Period field. - -`cy.confirmAddWidget()` - Confirm creation of a widget. - -`cy.removeWidget(name)` - Remove a widget on the page, specified by its name (title). - -### Widget - -`setTestCredentials(testCredentials, authToken)` - API call which adds new testCredentials - -`setTestEndpoints(testEndpoints, authToken)` - API call which adds new testEndpoints +# Cypress Automated Functional Tests + +## :construction: Work in progress :construction: + +Tests in this part of the repository are testing UI of the CogBoard. Get to know helper functions available right now (and update them if needed). + +## Running tests + +### CLI + +Quick use (i.e. before push): + +`./gradlew functionalTests` - will launch all specs, with local env config file. + +To override env add `-DcypressEnv=envName` system property. Which will run cypress config named `envName.json` under `functional/cypress-tests/cypress/config` directory. You must make sure that you created such a config in a first place. + +To customize config and specs to be launched, you have to run: + +`npx cypress run [--config-file path/to/config.json]`1 `[--spec path/to/spec.js]`2 from the `functional/cypress-tests` directory + +1 - provide custom config file path, by default it will use `functional/cypress-tests/cypress/config/local.json`. + +2 - provide spec file(s) to be launched. By default all specs are run. + +### GUI + +1. Go to `functional/cypress-tests` directory +1. Install Cypress `npm install` (run only once) +1. Open Cypress tools by executing `npx cypress open` + +## Contribution guide + +All cypress related commits should be done on branches starting with `automation/` + +### Tasks to do + +... + +### Coding conventions + +All tests should be written according to [Cypress.io Best Practices guide](https://docs.cypress.io/guides/references/best-practices.html 'Best Practices | Cypress Documentation'). Read it before starting to contribute. + +Additional project specific conventions: + +- For actions that have been already covered in other tests write `cy.request(...)` helper command to bypass UI and speed up the test execution time + - Good example would be the Widget tests. Dashboard creation and dashboard removal steps are performed in each of those tests and take around 2.5s. If we have used API calls instead we would be lookng at `2.5s * numOfWidgets` time reduction. +- Keep test data in separate files. i.e. `../fixtures/Widgets.js` +- No test case dependencies. Each test's expected initial state should be either prepared manually or by `cy.request(...)` call before test starts. +- Actions that impact on other specs (such as saving) should be kept in the same spec. +- Repetitive tests should be written in iterative manner: i.e. `../integration/widgets.js` or FE validation tests in `../integration/dashboards.js` +- ... + +## Available helpers + +Use helpers below to minimize specfiles size. Keep in mind that some of those helpers require certain state of app to work correctly, make sure you learn their code. + +### General + +`cy.saveState()` - Saves current state of the Dashboard + +### User + +`cy.login()` - Log in with credentials specified in configuration file. - _Currently `../cypress.json`_ + +`cy.logout()` - Log out of the application + +`getAuthenticationToken()` - Returns authentication token for username: admin + +`loginWithToken()` - Log in as admin + +### Dashboard + +`cy.openDrawer()` - Open the Dashboard Drawer + +`cy.closeDrawer()` - Close the Dashboard Drawer. + +`cy.chooseDashboard(dashboardName)` - Switch to the Dashboard named `dashboardName`. + +`cy.addDashboard(dashboardName, columnsCount, switchInterval, expectFailure)` - Add Dashboard with a name `dashboardName`, number of columns equal to `columnsCount`, switching interval set to `switchInterval` and `expectFailure` flag which will check if creation dialog has been closed when set to `false` or if creation dialog is still visible when set to `true`. + +`cy.removeDashboard(dashboardNname)` - Remove the Dashboard named `dashboardName`. + +`cy.renewDashboards(username, password)` - API call which generates dashboard specified in `../fixtures/reorderingConfig.json` + +### Widget + +`createWidget(name)` - use any name from `functional/cypress-tests/cypress/fixtures/Widgets.js` for example: `Widgets.whiteSpace.name` - this will add configured widget to current board. Widget type will be deducted from name prop. + +`cy.clickAddWidgetButton()` - Click the Add Widget button visible after login. + +`cy.fillNewWidgetGeneral(widgetType, title, newLine, disabled, columnsCount, rowsCount)` - Fill out the General Tab of the Widget Creation dialog. `widgetType` specifies Widget to be added, `title` is it's name, `newLine` will set the widget to be added on a next row, following the last existing widget on a Dashboard when set to `true`, `disabled` will make the widget disabled when set to `true`. `columnsCount` and `rowsCount` will determine number of, respectively, columns and rows. + +`cy.fillSchedulePeriod(value)` - Fill out the Schedule Period field. + +`cy.confirmAddWidget()` - Confirm creation of a widget. + +`cy.removeWidget(name)` - Remove a widget on the page, specified by its name (title). + +### Widget + +`setTestCredentials(testCredentials, authToken)` - API call which adds new testCredentials + +`setTestEndpoints(testEndpoints, authToken)` - API call which adds new testEndpoints diff --git a/functional/cypress-tests/cypress/integration/refresh_widgets.js b/functional/cypress-tests/cypress/integration/refresh_widgets.js index c5a9218d7..1805c6c50 100644 --- a/functional/cypress-tests/cypress/integration/refresh_widgets.js +++ b/functional/cypress-tests/cypress/integration/refresh_widgets.js @@ -1,57 +1,57 @@ -import { createWidget } from '../support/widget'; -import Widgets from '../fixtures/Widgets'; - -describe('Refresh widgets', () => { - const jenkinsJobTitleFirst = 'Jenkins Job Test'; - const blueColor = 'rgb(25, 140, 189)'; - const redColor = 'rgb(225, 49, 47)'; - const fakeMocksUrl = 'http://fake-mocks:1234'; - const apiMocksUrl = 'http://api-mocks:8080'; - let jenkinsJobWidget1; - - beforeEach(() => { - cy.visit('/'); - cy.login(); - - jenkinsJobWidget1 = createJenkinsJobWidget(jenkinsJobTitleFirst); - cy.saveState(); - }); - - it('Widget will be updated after edit endpoints', () => { - jenkinsJobWidget1.assertBackground(blueColor); - - changeUrls(fakeMocksUrl); - jenkinsJobWidget1.assertBackground(redColor); - - changeUrls(apiMocksUrl); - jenkinsJobWidget1.assertBackground(blueColor); - - jenkinsJobWidget1.remove(); - cy.saveState(); - }); - - function changeUrls(url) { - cy.openSettings(); - cy.contains('li.MuiListItem-container', 'API Mocks') - .find('[data-cy="edit-endpoint-edit-button"]') - .click(); - cy.get('[data-cy="endpoint-form-url-input"]') - .clear() - .type(url) - .blur(); - cy.get('[data-cy="endpoint-form-public-url-input"]') - .clear() - .type(url) - .blur(); - cy.get('[data-cy="endpoint-form-submit-button"]').click(); - cy.get('[data-cy="settings-menu-exit-button"]').click(); - } - - function createJenkinsJobWidget(title) { - cy.clickAddWidgetButton(); - const widget = createWidget(Widgets.jenkinsJob.name); - widget.title = title; - widget.configure(false); - return widget; - } -}); +import { createWidget } from '../support/widget'; +import Widgets from '../fixtures/Widgets'; + +describe('Refresh widgets', () => { + const jenkinsJobTitleFirst = 'Jenkins Job Test'; + const blueColor = 'rgb(25, 140, 189)'; + const redColor = 'rgb(225, 49, 47)'; + const fakeMocksUrl = 'http://fake-mocks:1234'; + const apiMocksUrl = 'http://api-mocks:8080'; + let jenkinsJobWidget1; + + beforeEach(() => { + cy.visit('/'); + cy.login(); + + jenkinsJobWidget1 = createJenkinsJobWidget(jenkinsJobTitleFirst); + cy.saveState(); + }); + + it('Widget will be updated after edit endpoints', () => { + jenkinsJobWidget1.assertBackground(blueColor); + + changeUrls(fakeMocksUrl); + jenkinsJobWidget1.assertBackground(redColor); + + changeUrls(apiMocksUrl); + jenkinsJobWidget1.assertBackground(blueColor); + + jenkinsJobWidget1.remove(); + cy.saveState(); + }); + + function changeUrls(url) { + cy.openSettings(); + cy.contains('li.MuiListItem-container', 'API Mocks') + .find('[data-cy="edit-endpoint-edit-button"]') + .click(); + cy.get('[data-cy="endpoint-form-url-input"]') + .clear() + .type(url) + .blur(); + cy.get('[data-cy="endpoint-form-public-url-input"]') + .clear() + .type(url) + .blur(); + cy.get('[data-cy="endpoint-form-submit-button"]').click(); + cy.get('[data-cy="settings-menu-exit-button"]').click(); + } + + function createJenkinsJobWidget(title) { + cy.clickAddWidgetButton(); + const widget = createWidget(Widgets.jenkinsJob.name); + widget.title = title; + widget.configure(false); + return widget; + } +}); diff --git a/functional/cypress-tests/cypress/support/widgets.js b/functional/cypress-tests/cypress/support/widgets.js index 841b5f6bc..96dd95809 100644 --- a/functional/cypress-tests/cypress/support/widgets.js +++ b/functional/cypress-tests/cypress/support/widgets.js @@ -1,59 +1,59 @@ -Cypress.Commands.add('clickAddWidgetButton', () => { - cy.get('[data-cy="main-template-add-widget-button"]').click(); -}); - -Cypress.Commands.add( - 'fillNewWidgetGeneral', - ( - type = 'Text', - title = 'Text Title', - newLine = false, - disabled = false, - columns = 2, - rows = 4 - ) => { - cy.get('[data-cy="widget-form-type-input"]').click(); - cy.contains('li', type).click(); - if (type !== 'Checkbox' && type !== 'White Space') { - cy.contains('span', type).should('is.visible'); - } - cy.get('[data-cy="widget-form-title-input"]') - .clear() - .type(title); - cy.get('[data-cy="widget-form-columns-input"]').type( - '{selectall}' + columns.toString() - ); - cy.get('[data-cy="widget-form-rows-input"]').type( - '{selectall}' + rows.toString() - ); - if (newLine == true) { - cy.get('[data-cy="widget-form-go-new-line-checkbox"]').click(); - } - if (disabled == true) { - cy.get('[data-cy="widget-form-disabled-input"]').click(); - } - } -); - -Cypress.Commands.add('fillSchedulePeriod', value => { - cy.get('[data-cy="widget-form-schedule-period-input"]').type( - '{selectall}' + value - ); -}); - -Cypress.Commands.add('confirmAddWidget', () => { - cy.get('[data-cy="widget-form-submit-button"]').click(); -}); - -Cypress.Commands.add('removeWidget', name => { - cy.contains(name) - .parents('.MuiCardHeader-root') - .trigger('mouseover') - .find('[data-cy="more-menu-button"]') - .click(); - cy.get('div[id="more-menu"]') - .not('[aria-hidden="true"]') - .find('[data-cy="widget-delete"]') - .click(); - cy.get('[data-cy="confirmation-dialog-ok"]').click(); -}); +Cypress.Commands.add('clickAddWidgetButton', () => { + cy.get('[data-cy="main-template-add-widget-button"]').click(); +}); + +Cypress.Commands.add( + 'fillNewWidgetGeneral', + ( + type = 'Text', + title = 'Text Title', + newLine = false, + disabled = false, + columns = 2, + rows = 4 + ) => { + cy.get('[data-cy="widget-form-type-input"]').click(); + cy.contains('li', type).click(); + if (type !== 'Checkbox' && type !== 'White Space') { + cy.contains('span', type).should('is.visible'); + } + cy.get('[data-cy="widget-form-title-input"]') + .clear() + .type(title); + cy.get('[data-cy="widget-form-columns-input"]').type( + '{selectall}' + columns.toString() + ); + cy.get('[data-cy="widget-form-rows-input"]').type( + '{selectall}' + rows.toString() + ); + if (newLine == true) { + cy.get('[data-cy="widget-form-go-new-line-checkbox"]').click(); + } + if (disabled == true) { + cy.get('[data-cy="widget-form-disabled-input"]').click(); + } + } +); + +Cypress.Commands.add('fillSchedulePeriod', value => { + cy.get('[data-cy="widget-form-schedule-period-input"]').type( + '{selectall}' + value + ); +}); + +Cypress.Commands.add('confirmAddWidget', () => { + cy.get('[data-cy="widget-form-submit-button"]').click(); +}); + +Cypress.Commands.add('removeWidget', name => { + cy.contains(name) + .parents('.MuiCardHeader-root') + .trigger('mouseover') + .find('[data-cy="more-menu-button"]') + .click(); + cy.get('div[id="more-menu"]') + .not('[aria-hidden="true"]') + .find('[data-cy="widget-delete"]') + .click(); + cy.get('[data-cy="confirmation-dialog-ok"]').click(); +}); From d1b0dc07abcfcffb8c959b7076e3580ca1f65127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Mon, 10 Jan 2022 02:40:59 +0100 Subject: [PATCH 192/226] Added documentation of LogViewerWidgetClass #448 --- .../widget/type/logviewer/LogsViewer.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md new file mode 100644 index 000000000..9e01b7efa --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md @@ -0,0 +1,46 @@ +# Logs Viewer widget + +## Classes +### LogViewerWidget +Main widget class, inheriting from BasicWidget. + +#### Constructor +An eventBus listener is created that handles sending logs upon launching widget by the user. +#### Non-inherited methods +- `updateWidget(fetchNewLogs: boolean)` + +Request logs from LogStorage is address was provided in widget configuration. +New logs should be fetched by LogStorage if `fetchNewLogs` is set to true + +- `sendResponse(logs: JsonObject)` + +Sends quarantine rules to front-end. + +- `buildConfiguration(config: JsonObject): LogStorageConfiguration` + +Transforms widget configuration `JsonObject` to `LogStorageConfiguration` by passing chosen fields +from config. + +- `determineConnectionStrategy(): ConnectionStrategy?` + +Uses `ConnectionStrategyFactory` to get correct strategy that will be used to connect +with endpoint providing logs. + +- `determineLogParsingStrategy()` + +Uses `LogParsingStrategyFactory` to get proper parser for logs, based on `LOG_PARSER` field from +configuration of the widget. + +### LogStorage +Class managing connection with database - it fetches logs from it and updates with new logs +delivered by `ConnectionStrategy` passed in constructor. + +#### Fields +- `enabledRegexes: List` + +Returns regexes of quarantine rules that are enabled at the moment. + +- `` + +#### Methods +- `` \ No newline at end of file From ad077cc6f2be9b83485f55dc674739428843ba85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Mon, 10 Jan 2022 11:15:59 +0100 Subject: [PATCH 193/226] Added default values for parameters #428 --- cogboard-local-compose.yml | 12 ++++++------ docker/Dockerfile | 4 ++-- gradle/prepareCogboardCompose.gradle.kts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 0a95e07ac..876a70d86 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -26,8 +26,8 @@ services: image: "cogboard/cogboard-app:${COGBOARD_VERSION}" environment: - COGBOARD_VERSION=${COGBOARD_VERSION} - - MONGO_USERNAME=${MONGO_USER} - - MONGO_PASSWORD=${MONGO_PASSWORD} + - MONGO_USERNAME=${MONGO_USER:-root} + - MONGO_PASSWORD=${MONGO_PASSWORD:-root} - MONGO_HOST=mongo - MONGO_PORT=27017 volumes: @@ -41,8 +41,8 @@ services: image: mongo:4 restart: always environment: - MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER} - MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD} + MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER:-root} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:-root} MONGO_INITDB_DATABASE: "logs" volumes: - "./mnt/mongo:/data/db" @@ -55,8 +55,8 @@ services: ports: - 8099:8081 environment: - ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_USER} - ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD} + ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_USER:-root} + ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD:-root} ME_CONFIG_MONGODB_URL: "mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/" networks: - cognet diff --git a/docker/Dockerfile b/docker/Dockerfile index 28f18960d..4f25b5a14 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ LABEL maintainer="CogBoard Project" COPY ./out/knotx /usr/local/knotx -#RUN sed -i 's/# JVM_DEBUG=/JVM_DEBUG=/g' /usr/local/knotx/bin/knotx -#RUN sed -i 's/suspend=n/suspend=y/g' /usr/local/knotx/bin/knotx +# RUN sed -i 's/# JVM_DEBUG=/JVM_DEBUG=/g' /usr/local/knotx/bin/knotx +# RUN sed -i 's/suspend=n/suspend=y/g' /usr/local/knotx/bin/knotx CMD [ "knotx", "run-knotx" ] \ No newline at end of file diff --git a/gradle/prepareCogboardCompose.gradle.kts b/gradle/prepareCogboardCompose.gradle.kts index 0e12fd6cd..f3657d035 100644 --- a/gradle/prepareCogboardCompose.gradle.kts +++ b/gradle/prepareCogboardCompose.gradle.kts @@ -9,8 +9,8 @@ fun createComposeFile() { val composeFilePath = "$rootDir/cogboard-compose.yml" logger.lifecycle(">> createZip >> Creating $composeFilePath") - val user = project.property("mongo.user") - val password = project.property("mongo.password") + val user = project.property("mongo.user") ?: "root" + val password = project.property("mongo.password") ?: "root" File(composeFilePath).writeText("""version: "3.7" services: From d442bc2c5fb7338e939c85e5194e520d08ffd11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Mon, 10 Jan 2022 21:13:45 +0100 Subject: [PATCH 194/226] Add date and time format --- .../src/components/widgets/dialogFields/TimestampInput.js | 3 ++- .../LogViewerWidget/Toolbar/DateRangePicker/CustomPicker.js | 3 ++- cogboard-webapp/src/constants/index.js | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js index ad810edfb..e1a3ed1df 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js @@ -12,6 +12,7 @@ import { StyledDateTimePicker, StyledIconButton } from './styled'; +import { DATE_TIME_FORMAT } from '../../../constants'; const TimestampInput = ({ error, @@ -43,7 +44,7 @@ const TimestampInput = ({ } error={hasError(error)} onChange={handleChange} - format="YYYY-MM-DD HH:mm" + format={DATE_TIME_FORMAT} ampm={false} helperText={ { const handleChange = data => onChange(data?.seconds(0).milliseconds(0)); @@ -15,7 +16,7 @@ const CustomPicker = ({ id, value, onChange, label, ...props }) => { data-cy={`date-time-picker-${id}`} value={value} onChange={handleChange} - format="YYYY-MM-DD HH:mm" + format={DATE_TIME_FORMAT} ampm={false} openTo="hours" label={label} diff --git a/cogboard-webapp/src/constants/index.js b/cogboard-webapp/src/constants/index.js index ff80aabe2..0161ecee3 100644 --- a/cogboard-webapp/src/constants/index.js +++ b/cogboard-webapp/src/constants/index.js @@ -347,3 +347,5 @@ export const NOTIFICATIONS = { duration: 3000 }) }; + +export const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; From c7c0c3d021faacc047d66313c58b7f7f9138a0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Mon, 10 Jan 2022 21:15:05 +0100 Subject: [PATCH 195/226] Simplify the toggling function --- .../Toolbar/QuarantineModal/index.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index b49bd808e..57615aa0f 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -43,14 +43,15 @@ const QuarantineModal = ({ wid, quarantine }) => { }; const toggleChecked = rule => { - const endTimestamp = rule.endTimestamp; - if (endTimestamp) { - const inFuture = moment.utc(endTimestamp * 1000).local() > moment(); - if (!inFuture) { - return { ...rule, checked: true, endTimestamp: null }; - } + const endTimestamp = + Number.isInteger(rule.endTimestamp) && + moment.utc(rule.endTimestamp * 1000).local(); + const disabledByEndTimestamp = endTimestamp && endTimestamp <= moment(); + if (disabledByEndTimestamp) { + return { ...rule, checked: true, endTimestamp: null }; + } else { + return { ...rule, checked: !rule.checked }; } - return { ...rule, checked: !rule.checked }; }; const handleQuarantineClick = event => { From bc6630139f59336db10e7e755c9e5a86e4675933 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 10 Jan 2022 21:21:28 +0100 Subject: [PATCH 196/226] Add copy button --- .../types/LogViewerWidget/LogList/LogEntry.js | 10 +++-- .../LogList/TextWithCopyButton.js | 41 +++++++++++++++++++ .../types/LogViewerWidget/LogList/styled.js | 32 ++++++++++++++- 3 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index 4f20bc71b..400c55b6b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -18,6 +18,7 @@ import { useSelector } from 'react-redux'; import { getIsAuthenticated } from '../../../../../selectors'; import { FilterList, Schedule } from '@material-ui/icons'; import { SimilarLogsContext } from '../context'; +import TextWithCopyButton from './TextWithCopyButton'; const LogEntry = ({ type, @@ -46,9 +47,10 @@ const LogEntry = ({ {variableData.map((entry, index) => { const entryText = description ? entry.description : entry.header; return ( - - {highlightText(entryText, search, HighlightedText)} - + ); })}
@@ -66,7 +68,7 @@ const LogEntry = ({ {type?.toUpperCase()} - {date} +
diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js new file mode 100644 index 000000000..e4766ca6e --- /dev/null +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; + +import FileCopyIcon from '@material-ui/icons/FileCopy'; +import { StyledCopyButton, Text, TextWithCopyButtonContainer } from './styled'; +import { Tooltip } from '@material-ui/core'; + +const tooltipMessages = { + standard: 'Copy to clipboard', + copied: 'Copied!' +}; + +const TextWithCopyButton = ({ text, ...props }) => { + const [tooltipMsg, setTooltipMsg] = useState(tooltipMessages.standard); + + const setStandardTooltipMsg = () => + setTimeout(() => setTooltipMsg(tooltipMessages.standard), 200); + + const setCopiedTooltipMsg = () => setTooltipMsg(tooltipMessages.copied); + + const handleCopy = () => + navigator.clipboard.writeText(text).then(() => setCopiedTooltipMsg()); + + return ( + + + { + e.stopPropagation(); + handleCopy(); + }} + > + + + + {text} + + ); +}; + +export default TextWithCopyButton; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 83163a14b..9c064bc07 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -22,13 +22,16 @@ export const GridSchema = styled.div` display: grid; grid-template-columns: 70px 150px 1fr 56px; padding: 0 10px; + justify-items: flex-start; `; export const VariableGridSchema = styled.div( props => ` width: 100%; display: grid; + justify-items: flex-start; + align-items: flex-start; grid-template-columns: ${props.template}; - ${props.skipColumns ? 'grid-column: 3 / 4' : ''} + ${props.skipColumns ? 'grid-column: 3 / 4;' : ''} ` ); @@ -121,6 +124,7 @@ export const HighlightMark = styled.div` export const SimilarLogsButtonsContainer = styled.div` display: flex; + justify-self: end; flex-direction: column; align-items: flex-end; margin: 8px 0; @@ -144,3 +148,29 @@ export const QuarantineSimilarLogsButton = styled(IconButton)` QuarantineSimilarLogsButton.defaultProps = { size: 'small' }; + +export const StyledCopyButton = styled(IconButton)` + position: absolute; + &&& { + top: 0.1rem; + right: -1rem; + } + padding: 0; + font-size: 14px; + + visiblity: hidden; + opacity: 0; + transition: opacity 0.2s ease-in-out; +`; +StyledCopyButton.defaultProps = { + size: 'small' +}; + +export const TextWithCopyButtonContainer = styled.div` + position: relative; + + &:hover ${StyledCopyButton} { + opacity: 1; + visiblity: visible; + } +`; From 991133c17595f34f23fefd28b079781bf57911d2 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 10 Jan 2022 21:26:03 +0100 Subject: [PATCH 197/226] Fix search bar clear icon --- .../widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js index 0458a5939..2fb02ebdd 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/styled.js @@ -22,7 +22,7 @@ export const StyledTextField = styled(TextField)` min-width: 5rem; width: 100%; - & > input { + & input { margin-right: 1.8rem; } `; From c13be5b5291e7aee16b9c03e1fafbd8540d428a5 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 10 Jan 2022 22:57:58 +0100 Subject: [PATCH 198/226] Fix ToggleIconButton attribute warning --- .../widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js | 2 +- .../components/widgets/types/LogViewerWidget/Toolbar/styled.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js index f69276550..c75e08042 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/ToggleIconButton.js @@ -6,7 +6,7 @@ import { StyledIconButton } from './styled'; const ToggleIconButton = ({ tooltip, Icon, enabled, ...props }) => { return ( - + diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js index 6f2364b79..bf8defe95 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/styled.js @@ -17,7 +17,7 @@ export const Wrapper = styled.div` export const StyledIconButton = styled(IconButton)` ${props => - props.enabled + props.enabled === true.toString() ? ` color: ${COLORS.BLUE}; background-color: ${COLORS.LIGHT_SHADE} From 8c31e3dda9859acd45747a0d5d552ef6097ba7cd Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 10 Jan 2022 23:53:40 +0100 Subject: [PATCH 199/226] Make log accordion controlled --- .../types/LogViewerWidget/LogList/LogEntry.js | 36 ++++++++++++++----- .../types/LogViewerWidget/LogList/index.js | 4 ++- .../widgets/types/LogViewerWidget/helpers.js | 8 ++++- .../widgets/types/LogViewerWidget/index.js | 20 ++++------- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index 4f20bc71b..cdd939c13 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,5 +1,13 @@ -import React, { useState, useContext } from 'react'; -import { string, number, bool, shape, oneOfType, arrayOf } from 'prop-types'; +import React, { useContext } from 'react'; +import { + string, + number, + bool, + shape, + oneOfType, + arrayOf, + func +} from 'prop-types'; import { getGridTemplate, highlightText } from './helpers'; import { AccordionSummary, AccordionDetails, Tooltip } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; @@ -20,6 +28,9 @@ import { FilterList, Schedule } from '@material-ui/icons'; import { SimilarLogsContext } from '../context'; const LogEntry = ({ + id, + expanded, + toggleExpanded, type, date, variableData, @@ -27,9 +38,7 @@ const LogEntry = ({ search, highlight }) => { - const [expanded, setExpanded] = useState(false); const isAuthenticated = useSelector(getIsAuthenticated); - const similarLogs = useContext(SimilarLogsContext); const getLastVariableHeader = () => @@ -56,9 +65,9 @@ const LogEntry = ({ }; return ( - + setExpanded(!expanded)} + onClick={toggleExpanded} expandIcon={expanded && } > {highlight && } @@ -102,6 +111,9 @@ const LogEntry = ({ export default LogEntry; LogEntry.propTypes = { + id: string.isRequired, + expanded: bool.isRequired, + toggleExpanded: func, type: string, date: string.isRequired, variableData: arrayOf( @@ -109,11 +121,17 @@ LogEntry.propTypes = { header: oneOfType([string, number, bool]).isRequired, description: oneOfType([string, number, bool]).isRequired }) - ) + ), + template: arrayOf(string), + search: string, + highlight: bool }; LogEntry.defaultProps = { + toggleExpanded: () => {}, type: 'info', - date: '0', - variableData: [] + variableData: [], + template: [], + search: undefined, + highlight: false }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 828a68a7f..6e4ceb000 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -15,6 +15,7 @@ import { export default function LogList({ logs, template, + toggleExpandLog, search, shouldFollowLogs, handleFollowChange @@ -55,8 +56,9 @@ export default function LogList({ const log = logs[index]; return ( toggleExpandLog(log._id)} type={log.type} date={log.date} variableData={log.variableData} diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js index f27fec341..51ffab882 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js @@ -1,9 +1,15 @@ export const joinLogs = (currentLogs, newLogs, logLines) => { let joined = currentLogs; - newLogs.forEach(newLog => { + addAccordionControler(newLogs).forEach(newLog => { if (!joined.some(log => log._id === newLog._id)) { joined.push(newLog); } }); return joined.slice(-logLines); }; + +export const addAccordionControler = logs => + logs?.map(log => ({ ...log, expanded: false })); + +export const toggleAccordion = (logs, id) => + logs.map(log => (log._id === id ? { ...log, expanded: !log.expanded } : log)); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index b724f67c3..f24d73f4f 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -7,7 +7,7 @@ import Toolbar from './Toolbar'; import LogList from './LogList'; import { Container } from './styled'; import { getInitialLogs } from '../../../../utils/fetch'; -import { joinLogs } from './helpers'; +import { addAccordionControler, joinLogs, toggleAccordion } from './helpers'; import { SimilarLogsContext } from './context'; import { getFilters, getLevel } from './Toolbar/FilterPicker/helpers'; import { getDateSpan } from './Toolbar/DateRangePicker/helpers'; @@ -17,12 +17,11 @@ import { filterByLevel } from './LogList/helpers'; -const LogViewerWidget = ({ id }) => { +const LogViewerWidget = ({ id, logLinesField }) => { const widgetData = useSelector( ({ widgets }) => widgets.widgetsById[id], shallowEqual ); - useEffect(() => console.log(widgetData), [widgetData]); const [widgetLocalStorageData, setWidgetLocalStorage] = useLocalStorage(id); const widgetLocalStorage = { @@ -34,20 +33,18 @@ const LogViewerWidget = ({ id }) => { const [shouldFollowLogs, setFollow] = useState(true); useEffect(() => { - getInitialLogs(id).then(logs => { - setStoredLogs(logs); - }); + getInitialLogs(id).then(logs => setStoredLogs(addAccordionControler(logs))); }, [id]); const newLogs = widgetData.content?.logs || []; const template = widgetData.content?.variableFields; const quarantine = widgetData.content?.quarantineRules || []; - const logLines = widgetData.logLinesField || 1000; const [storedLogs, setStoredLogs] = useState([]); + const toggleExpandLog = id => setStoredLogs(toggleAccordion(storedLogs, id)); useEffect(() => { - setStoredLogs(joinLogs(storedLogs, newLogs, logLines)); + setStoredLogs(joinLogs(storedLogs, newLogs, logLinesField)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [widgetData]); @@ -80,17 +77,14 @@ const LogViewerWidget = ({ id }) => { setSearchFilter={setSearchFilter} shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} - lastLog={ - storedLogs && - storedLogs.length > 0 && - storedLogs[storedLogs.length - 1] - } + lastLog={storedLogs?.length > 0 && storedLogs[storedLogs.length - 1]} logs={filteredLogs} /> {storedLogs && ( Date: Tue, 11 Jan 2022 13:50:25 +0100 Subject: [PATCH 200/226] Update copy button placement --- .../components/widgets/types/LogViewerWidget/LogList/styled.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index 9c064bc07..b7ac7361e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -153,7 +153,7 @@ export const StyledCopyButton = styled(IconButton)` position: absolute; &&& { top: 0.1rem; - right: -1rem; + right: -1.15rem; } padding: 0; font-size: 14px; From 99f24b3ed572fb523622e0700e87682e2937226d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 11 Jan 2022 18:44:26 +0100 Subject: [PATCH 201/226] Add comments, rename variable --- .../src/components/widgets/dialogFields/TimestampInput.js | 2 +- .../LogViewerWidget/Toolbar/QuarantineModal/index.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js index e1a3ed1df..e8d5345ba 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js @@ -37,7 +37,7 @@ const TimestampInput = ({ value={ Number.isInteger(value) ? moment - .utc(value * 1000) + .utc(value * 1000) // seconds to milliseconds .local() .format() : null diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index 57615aa0f..633bbdca0 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -36,7 +36,7 @@ const QuarantineModal = ({ wid, quarantine }) => { const isChecked = (checked, endTimestamp) => { if (endTimestamp) { - const inFuture = moment.utc(endTimestamp * 1000).local() > moment(); + const inFuture = moment.utc(endTimestamp * 1000).local() > moment(); // seconds to milliseconds return checked && inFuture; } return checked; @@ -45,9 +45,9 @@ const QuarantineModal = ({ wid, quarantine }) => { const toggleChecked = rule => { const endTimestamp = Number.isInteger(rule.endTimestamp) && - moment.utc(rule.endTimestamp * 1000).local(); - const disabledByEndTimestamp = endTimestamp && endTimestamp <= moment(); - if (disabledByEndTimestamp) { + moment.utc(rule.endTimestamp * 1000).local(); // seconds to milliseconds + const shouldSkipEndTimestamp = endTimestamp && endTimestamp <= moment(); + if (shouldSkipEndTimestamp) { return { ...rule, checked: true, endTimestamp: null }; } else { return { ...rule, checked: !rule.checked }; From 9c30f312a757bf8968439a1f2fe05f926d4d615b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 12 Jan 2022 20:24:19 +0100 Subject: [PATCH 202/226] Add milliseconds variable --- .../src/components/widgets/dialogFields/TimestampInput.js | 4 ++-- .../types/LogViewerWidget/Toolbar/QuarantineModal/index.js | 6 ++++-- cogboard-webapp/src/constants/index.js | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js index e8d5345ba..dbee47d6d 100644 --- a/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js +++ b/cogboard-webapp/src/components/widgets/dialogFields/TimestampInput.js @@ -12,7 +12,7 @@ import { StyledDateTimePicker, StyledIconButton } from './styled'; -import { DATE_TIME_FORMAT } from '../../../constants'; +import { DATE_TIME_FORMAT, MILLIS_IN_SECOND } from '../../../constants'; const TimestampInput = ({ error, @@ -37,7 +37,7 @@ const TimestampInput = ({ value={ Number.isInteger(value) ? moment - .utc(value * 1000) // seconds to milliseconds + .utc(value * MILLIS_IN_SECOND) .local() .format() : null diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index 633bbdca0..62c13411a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -20,6 +20,7 @@ import EditQFilter from './EditQFilter'; import DeleteItem from '../../../../../DeleteItem'; import { SimilarLogsContext } from '../../context'; import moment from 'moment-timezone'; +import { MILLIS_IN_SECOND } from '../../../../../../constants'; const QuarantineModal = ({ wid, quarantine }) => { const isAuthenticated = useSelector(getIsAuthenticated); @@ -36,7 +37,8 @@ const QuarantineModal = ({ wid, quarantine }) => { const isChecked = (checked, endTimestamp) => { if (endTimestamp) { - const inFuture = moment.utc(endTimestamp * 1000).local() > moment(); // seconds to milliseconds + const inFuture = + moment.utc(endTimestamp * MILLIS_IN_SECOND).local() > moment(); return checked && inFuture; } return checked; @@ -45,7 +47,7 @@ const QuarantineModal = ({ wid, quarantine }) => { const toggleChecked = rule => { const endTimestamp = Number.isInteger(rule.endTimestamp) && - moment.utc(rule.endTimestamp * 1000).local(); // seconds to milliseconds + moment.utc(rule.endTimestamp * MILLIS_IN_SECOND).local(); const shouldSkipEndTimestamp = endTimestamp && endTimestamp <= moment(); if (shouldSkipEndTimestamp) { return { ...rule, checked: true, endTimestamp: null }; diff --git a/cogboard-webapp/src/constants/index.js b/cogboard-webapp/src/constants/index.js index 0161ecee3..6ac457451 100644 --- a/cogboard-webapp/src/constants/index.js +++ b/cogboard-webapp/src/constants/index.js @@ -349,3 +349,4 @@ export const NOTIFICATIONS = { }; export const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; +export const MILLIS_IN_SECOND = 1000; From 3fb3a5657b7252a1a9c3a0959064481528e108a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Wed, 12 Jan 2022 23:45:04 +0100 Subject: [PATCH 203/226] Update container names, add logs --- .../cogboard/logStorage/LogController.kt | 17 +++++++++++- .../cogboard/logStorage/LogStorage.kt | 2 +- cogboard-local-compose.yml | 26 +++++++++---------- gradle/prepareCogboardCompose.gradle.kts | 4 +-- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt index 134ad99c4..ee6c0110d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogController.kt @@ -10,8 +10,11 @@ import io.knotx.server.api.handler.RoutingHandlerFactory import io.vertx.core.Handler import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject +import io.vertx.core.logging.Logger +import io.vertx.core.logging.LoggerFactory import io.vertx.reactivex.core.Vertx import io.vertx.reactivex.ext.web.RoutingContext +import java.time.Instant class LogController : RoutingHandlerFactory { @@ -33,7 +36,15 @@ class LogController : RoutingHandlerFactory { .getInteger(Props.LOG_LINES) ?: LogViewerWidget.DEFAULT_LOG_LINES.toInt() - return fetchLogs(id, logLines) + return if (LOGGER.isDebugEnabled) { + val start = Instant.now() + val logs = fetchLogs(id, logLines) + val took = Instant.now().minusMillis(start.toEpochMilli()).toEpochMilli() + LOGGER.debug("DB query for $id took $took[ms] for getting $logLines (processed logs: ${logs.size})") + logs + } else { + fetchLogs(id, logLines) + } } private fun fetchLogs(id: String, logLines: Int): List { @@ -45,4 +56,8 @@ class LogController : RoutingHandlerFactory { .map { it.asLog() } .sortedBy { it.seq } } + + companion object { + val LOGGER: Logger = LoggerFactory.getLogger(LogController::class.java) + } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt index 6fc99c2f0..f977e28a4 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/logStorage/LogStorage.kt @@ -247,7 +247,7 @@ class LogStorage( private const val MONGO_SCHEME = "mongodb" private val MONGO_USERNAME = System.getenv("MONGO_USERNAME") ?: "root" private val MONGO_PASSWORD = System.getenv("MONGO_PASSWORD") ?: "root" - private val MONGO_HOST = System.getenv("MONGO_HOST") ?: "mongo" + private val MONGO_HOST = System.getenv("MONGO_HOST") ?: "mongo-logs-storage" private val MONGO_PORT = System.getenv("MONGO_PORT")?.toIntOrNull() ?: 27017 /** Returns a shared instance of the Mongo client. */ diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 876a70d86..18b44b327 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -28,7 +28,7 @@ services: - COGBOARD_VERSION=${COGBOARD_VERSION} - MONGO_USERNAME=${MONGO_USER:-root} - MONGO_PASSWORD=${MONGO_PASSWORD:-root} - - MONGO_HOST=mongo + - MONGO_HOST=mongo-logs-storage - MONGO_PORT=27017 volumes: - "./mnt:/data" @@ -37,7 +37,7 @@ services: # ports: # - "18092:18092" - mongo: + mongo-logs-storage: image: mongo:4 restart: always environment: @@ -49,17 +49,17 @@ services: networks: - cognet - mongo-express: - image: mongo-express - restart: always - ports: - - 8099:8081 - environment: - ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_USER:-root} - ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD:-root} - ME_CONFIG_MONGODB_URL: "mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/" - networks: - - cognet + # mongo-logs-storage-viewer: + # image: mongo-express + # restart: always + # ports: + # - 8099:8081 + # environment: + # ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_USER:-root} + # ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD:-root} + # ME_CONFIG_MONGODB_URL: "mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/" + # networks: + # - cognet frontend: image: "cogboard/cogboard-web:${COGBOARD_VERSION}" diff --git a/gradle/prepareCogboardCompose.gradle.kts b/gradle/prepareCogboardCompose.gradle.kts index f3657d035..236b9baad 100644 --- a/gradle/prepareCogboardCompose.gradle.kts +++ b/gradle/prepareCogboardCompose.gradle.kts @@ -23,8 +23,8 @@ services: volumes: - "./mnt:/data" - mongo: - image: mongo + mongo-logs-storage: + image: mongo:4 restart: always environment: MONGO_INITDB_ROOT_USERNAME: "$user" From 976b61f8a843fb86e72a64bbc6e3c57ee5d5ef2a Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 13 Jan 2022 15:15:52 +0100 Subject: [PATCH 204/226] Fixes, following logs uses virtuoso --- .../types/LogViewerWidget/LogList/LogEntry.js | 84 ++++++++++--------- .../types/LogViewerWidget/LogList/helpers.js | 4 +- .../types/LogViewerWidget/LogList/index.js | 70 ++++++++-------- .../types/LogViewerWidget/LogList/styled.js | 11 ++- .../widgets/types/LogViewerWidget/index.js | 5 +- 5 files changed, 96 insertions(+), 78 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index b2c780c15..d576f5f05 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -20,7 +20,8 @@ import { HighlightMark, SimilarLogsButtonsContainer, FilterSimilarLogsButton, - QuarantineSimilarLogsButton + QuarantineSimilarLogsButton, + LogMargin } from './styled'; import { useSelector } from 'react-redux'; import { getIsAuthenticated } from '../../../../../selectors'; @@ -67,46 +68,51 @@ const LogEntry = ({ }; return ( - - } - > - {highlight && } - - - {type?.toUpperCase()} - - - - - - - - - - - similarLogs.setFilter(getLastVariableHeader())} - > - - - - {isAuthenticated && ( - - - similarLogs.setQuarantine(getLastVariableHeader()) - } + + + } + > + {highlight && } + + + {type?.toUpperCase()} + + + + + + + + + + + similarLogs.setFilter(getLastVariableHeader())} > - - + + - )} - - - - + {isAuthenticated && ( + + + similarLogs.setQuarantine(getLastVariableHeader()) + } + > + + + + )} + + + + + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js index 8ac9cbf9d..9feb7344b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/helpers.js @@ -24,7 +24,9 @@ const getLogTexts = log => { }; export const isLogHighlighted = (log, search) => - search && getLogTexts(log).some(text => text.match(new RegExp(search, 'i'))); + search && getLogTexts(log).some(text => text.match(new RegExp(search, 'i'))) + ? true + : false; export const highlightText = (text, search, Component) => search diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 6e4ceb000..9f41d4ff7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, useState } from 'react'; +import React, { useRef, useEffect } from 'react'; import { getGridTemplate, isLogHighlighted } from './helpers'; import { useTheme } from '@material-ui/core'; import LogEntry from './LogEntry'; @@ -21,8 +21,9 @@ export default function LogList({ handleFollowChange }) { const theme = useTheme(); + const virtuosoRef = useRef(null); const scrollerRef = useRef(null); - const [scroll, setScroll] = useState(0); + const listScrollPos = useRef(0); const VariableLogListHeader = () => ( @@ -32,42 +33,38 @@ export default function LogList({ ); - useEffect(() => { - if (shouldFollowLogs) { - scrollerRef.current.scrollTo({ - top: scrollerRef.current.scrollHeight, - behavior: 'smooth' - }); - } - setScroll(scrollerRef.current.scrollTop); - }, [logs, shouldFollowLogs, scroll]); - - const stopFollowingOnUpScroll = () => { - if (scroll > scrollerRef.current.scrollTop) { + const handleScroll = () => { + const isScrollingUpward = + listScrollPos.current > scrollerRef.current.scrollTop; + if (isScrollingUpward) { handleFollowChange(false); } - setScroll(scrollerRef.current.scrollTop); + listScrollPos.current = scrollerRef.current.scrollTop; }; - const handleScrollChange = isScrolling => - isScrolling && stopFollowingOnUpScroll(); + useEffect(() => { + if (shouldFollowLogs) { + virtuosoRef.current.scrollToIndex(logs.length - 1); + } + }, [shouldFollowLogs]); + + const getLogByIndex = index => ( + + ); - const getLogByIndex = index => { - const log = logs[index]; - return ( - toggleExpandLog(log._id)} - type={log.type} - date={log.date} - variableData={log.variableData} - template={template} - search={search} - highlight={isLogHighlighted(log, search)} - /> - ); - }; + const MemoLogEntry = React.memo(({ log }) => ( + toggleExpandLog(log._id)} + type={log.type} + date={log.date} + variableData={log.variableData} + template={template} + search={search} + highlight={isLogHighlighted(log, search)} + /> + )); return ( @@ -81,11 +78,16 @@ export default function LogList({ (scrollerRef.current = ref)} - isScrolling={handleScrollChange} + isScrolling={handleScroll} totalCount={logs.length} increaseViewportBy={300} // defines loading overlap (in pixels) itemContent={getLogByIndex} + atBottomThreshold={0} + followOutput={isAtBottom => + shouldFollowLogs && !isAtBottom ? 'smooth' : false + } /> diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index b7ac7361e..d4dea3e60 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -72,14 +72,15 @@ export const StyledVirtuoso = styled(Virtuoso)` `; export const CustomAccordion = styled(Accordion)` - margin: 2px 0; + margin: 0; + box-shadow: none; &.MuiPaper-root { background-color: ${COLORS.LIGHT_SHADE}; overflow: hidden; } &&.Mui-expanded { - margin: 0.5em 0; + margin: 0; } .MuiAccordionSummary-root { @@ -151,7 +152,7 @@ QuarantineSimilarLogsButton.defaultProps = { export const StyledCopyButton = styled(IconButton)` position: absolute; - &&& { + &&&& { top: 0.1rem; right: -1.15rem; } @@ -174,3 +175,7 @@ export const TextWithCopyButtonContainer = styled.div` visiblity: visible; } `; + +export const LogMargin = styled.div` + padding: 1px 0; +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index f24d73f4f..df54d2118 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -41,7 +41,10 @@ const LogViewerWidget = ({ id, logLinesField }) => { const quarantine = widgetData.content?.quarantineRules || []; const [storedLogs, setStoredLogs] = useState([]); - const toggleExpandLog = id => setStoredLogs(toggleAccordion(storedLogs, id)); + const toggleExpandLog = id => { + setStoredLogs(toggleAccordion(storedLogs, id)); + setFollow(false); + }; useEffect(() => { setStoredLogs(joinLogs(storedLogs, newLogs, logLinesField)); From da5c3261feb60b7f716360058b9244b3314e24c2 Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 14 Jan 2022 00:14:37 +0100 Subject: [PATCH 205/226] Add list of expanded logs to redux --- cogboard-webapp/src/actions/actionCreators.js | 8 +++- cogboard-webapp/src/actions/types.js | 1 + .../types/LogViewerWidget/LogList/LogEntry.js | 43 +++++++++++++------ .../types/LogViewerWidget/LogList/index.js | 7 +-- .../widgets/types/LogViewerWidget/helpers.js | 10 +---- .../widgets/types/LogViewerWidget/index.js | 32 ++++++-------- cogboard-webapp/src/reducers/widgets/index.js | 4 +- .../src/reducers/widgets/logsViewers.js | 38 ++++++++++++++++ 8 files changed, 98 insertions(+), 45 deletions(-) create mode 100644 cogboard-webapp/src/reducers/widgets/logsViewers.js diff --git a/cogboard-webapp/src/actions/actionCreators.js b/cogboard-webapp/src/actions/actionCreators.js index 9d0cfcc88..a24e45ad9 100644 --- a/cogboard-webapp/src/actions/actionCreators.js +++ b/cogboard-webapp/src/actions/actionCreators.js @@ -30,7 +30,8 @@ import { ADD_SETTINGS_ITEM, EDIT_SETTINGS_ITEM, DELETE_SETTINGS_ITEM, - WAITING_FOR_NEW_VERSION_DATA + WAITING_FOR_NEW_VERSION_DATA, + TOGGLE_LOGS_VIEWER_LOG } from './types'; import { INITIAL_BOARD_PROPS } from '../constants'; @@ -193,3 +194,8 @@ export const waitingForNewVersion = data => ({ type: WAITING_FOR_NEW_VERSION_DATA, payload: data }); + +export const toggleLogsViewerLog = ({ wid, logid }) => ({ + type: TOGGLE_LOGS_VIEWER_LOG, + payload: { wid, logid } +}); diff --git a/cogboard-webapp/src/actions/types.js b/cogboard-webapp/src/actions/types.js index 93823f81d..fe3445f82 100644 --- a/cogboard-webapp/src/actions/types.js +++ b/cogboard-webapp/src/actions/types.js @@ -30,3 +30,4 @@ export const ADD_SETTINGS_ITEM = 'ADD_SETTINGS_ITEM'; export const EDIT_SETTINGS_ITEM = 'EDIT_SETTINGS_ITEM'; export const DELETE_SETTINGS_ITEM = 'DELETE_SETTINGS_ITEM'; export const WAITING_FOR_NEW_VERSION_DATA = 'WAITING_FOR_NEW_VERSION_DATA'; +export const TOGGLE_LOGS_VIEWER_LOG = 'TOGGLE_LOGS_VIEWER_LOG'; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index d576f5f05..1d22d3908 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -1,4 +1,7 @@ import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { toggleLogsViewerLog } from '../../../../../actions/actionCreators'; +import { getIsAuthenticated } from '../../../../../selectors'; import { string, number, @@ -8,9 +11,11 @@ import { arrayOf, func } from 'prop-types'; +import { SimilarLogsContext } from '../context'; import { getGridTemplate, highlightText } from './helpers'; + import { AccordionSummary, AccordionDetails, Tooltip } from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { FilterList, Schedule, ExpandMore } from '@material-ui/icons'; import { GridSchema, Text, @@ -23,16 +28,12 @@ import { QuarantineSimilarLogsButton, LogMargin } from './styled'; -import { useSelector } from 'react-redux'; -import { getIsAuthenticated } from '../../../../../selectors'; -import { FilterList, Schedule } from '@material-ui/icons'; -import { SimilarLogsContext } from '../context'; import TextWithCopyButton from './TextWithCopyButton'; const LogEntry = ({ + wid, + onToggle, id, - expanded, - toggleExpanded, type, date, variableData, @@ -40,9 +41,20 @@ const LogEntry = ({ search, highlight }) => { + const dispatch = useDispatch(); const isAuthenticated = useSelector(getIsAuthenticated); const similarLogs = useContext(SimilarLogsContext); + const expandedList = + useSelector(store => store.widgets.logsViewersCache[wid]?.expandedLogs) || + []; + const isExpanded = expandedList.includes(id); + + const toggleExpanded = () => { + dispatch(toggleLogsViewerLog({ wid, logid: id })); + onToggle(); + }; + const getLastVariableHeader = () => variableData[variableData.length - 1]?.header ?? ''; @@ -69,10 +81,15 @@ const LogEntry = ({ return ( - + } + expandIcon={isExpanded && } > {highlight && } @@ -119,9 +136,8 @@ const LogEntry = ({ export default LogEntry; LogEntry.propTypes = { + wid: string.isRequired, id: string.isRequired, - expanded: bool.isRequired, - toggleExpanded: func, type: string, date: string.isRequired, variableData: arrayOf( @@ -132,11 +148,12 @@ LogEntry.propTypes = { ), template: arrayOf(string), search: string, - highlight: bool + highlight: bool, + onToggle: func }; LogEntry.defaultProps = { - toggleExpanded: () => {}, + onToggle: () => {}, type: 'info', variableData: [], template: [], diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 9f41d4ff7..0b1b7f96e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -13,9 +13,9 @@ import { } from './styled'; export default function LogList({ + wid, logs, template, - toggleExpandLog, search, shouldFollowLogs, handleFollowChange @@ -54,15 +54,16 @@ export default function LogList({ const MemoLogEntry = React.memo(({ log }) => ( toggleExpandLog(log._id)} type={log.type} date={log.date} variableData={log.variableData} template={template} search={search} highlight={isLogHighlighted(log, search)} + onToggle={() => handleFollowChange(false)} /> )); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js index 51ffab882..8471f43a3 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js @@ -1,15 +1,9 @@ export const joinLogs = (currentLogs, newLogs, logLines) => { - let joined = currentLogs; - addAccordionControler(newLogs).forEach(newLog => { + const joined = currentLogs; + newLogs.forEach(newLog => { if (!joined.some(log => log._id === newLog._id)) { joined.push(newLog); } }); return joined.slice(-logLines); }; - -export const addAccordionControler = logs => - logs?.map(log => ({ ...log, expanded: false })); - -export const toggleAccordion = (logs, id) => - logs.map(log => (log._id === id ? { ...log, expanded: !log.expanded } : log)); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index df54d2118..27d783ba1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -7,7 +7,7 @@ import Toolbar from './Toolbar'; import LogList from './LogList'; import { Container } from './styled'; import { getInitialLogs } from '../../../../utils/fetch'; -import { addAccordionControler, joinLogs, toggleAccordion } from './helpers'; +import { joinLogs } from './helpers'; import { SimilarLogsContext } from './context'; import { getFilters, getLevel } from './Toolbar/FilterPicker/helpers'; import { getDateSpan } from './Toolbar/DateRangePicker/helpers'; @@ -17,7 +17,7 @@ import { filterByLevel } from './LogList/helpers'; -const LogViewerWidget = ({ id, logLinesField }) => { +const LogViewerWidget = ({ id }) => { const widgetData = useSelector( ({ widgets }) => widgets.widgetsById[id], shallowEqual @@ -33,23 +33,20 @@ const LogViewerWidget = ({ id, logLinesField }) => { const [shouldFollowLogs, setFollow] = useState(true); useEffect(() => { - getInitialLogs(id).then(logs => setStoredLogs(addAccordionControler(logs))); + getInitialLogs(id).then(logs => setStoredLogs(logs)); }, [id]); const newLogs = widgetData.content?.logs || []; + const logLinesField = widgetData.logLinesField; const template = widgetData.content?.variableFields; const quarantine = widgetData.content?.quarantineRules || []; const [storedLogs, setStoredLogs] = useState([]); - const toggleExpandLog = id => { - setStoredLogs(toggleAccordion(storedLogs, id)); - setFollow(false); - }; useEffect(() => { setStoredLogs(joinLogs(storedLogs, newLogs, logLinesField)); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [widgetData]); + }, [widgetData.content?.logs]); const [filterSimilarLogs, setFilterSimilarLogs] = useState(null); const [quarantineSimilarLogs, setQuarantineSimilarLogs] = useState(null); @@ -83,17 +80,14 @@ const LogViewerWidget = ({ id, logLinesField }) => { lastLog={storedLogs?.length > 0 && storedLogs[storedLogs.length - 1]} logs={filteredLogs} /> - {storedLogs && ( - - )} + ); diff --git a/cogboard-webapp/src/reducers/widgets/index.js b/cogboard-webapp/src/reducers/widgets/index.js index 06d145c87..5b5ba2544 100644 --- a/cogboard-webapp/src/reducers/widgets/index.js +++ b/cogboard-webapp/src/reducers/widgets/index.js @@ -2,10 +2,12 @@ import { combineReducers } from 'redux'; import widgetsById from './widgetsById'; import widgetTypes from './widgetTypes'; +import logsViewersCache from './logsViewers'; const widgets = combineReducers({ widgetsById, - widgetTypes + widgetTypes, + logsViewersCache }); export default widgets; diff --git a/cogboard-webapp/src/reducers/widgets/logsViewers.js b/cogboard-webapp/src/reducers/widgets/logsViewers.js new file mode 100644 index 000000000..7f12d00c1 --- /dev/null +++ b/cogboard-webapp/src/reducers/widgets/logsViewers.js @@ -0,0 +1,38 @@ +import { TOGGLE_LOGS_VIEWER_LOG } from '../../actions/types'; + +const toggleLogsViewerLog = (state, { payload }) => { + const { wid, logid } = payload; + const expandedLogs = state[wid]?.expandedLogs || []; + + const removeFromList = (index, list) => [ + ...list.slice(0, index), + ...list.slice(index + 1, list.length) + ]; + + const index = expandedLogs.indexOf(logid); + const logCollapsed = index === -1; + const newExpandedLogs = logCollapsed + ? [...expandedLogs, logid] + : removeFromList(index, expandedLogs); + + return { + ...state, + [wid]: { + ...state[wid], + expandedLogs: newExpandedLogs + } + }; +}; + +const logsViewers = (state = {}, action) => { + const { type } = action; + + switch (type) { + case TOGGLE_LOGS_VIEWER_LOG: + return toggleLogsViewerLog(state, action); + default: + return state; + } +}; + +export default logsViewers; From 64cb74d09fff2208b77db28df8b6dc2307a7e2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Fri, 14 Jan 2022 00:16:28 +0100 Subject: [PATCH 206/226] Add chmod script, add a copy using slice --- .../src/components/widgets/types/LogViewerWidget/helpers.js | 2 +- ssh/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js index f27fec341..8755f3773 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js @@ -1,5 +1,5 @@ export const joinLogs = (currentLogs, newLogs, logLines) => { - let joined = currentLogs; + let joined = currentLogs.slice(); newLogs.forEach(newLog => { if (!joined.some(log => log._id === newLog._id)) { joined.push(newLog); diff --git a/ssh/Dockerfile b/ssh/Dockerfile index 13b47cb3f..18e01e152 100644 --- a/ssh/Dockerfile +++ b/ssh/Dockerfile @@ -6,6 +6,6 @@ ENV USER_NAME=mock ENV USER_PASSWORD=TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E ADD gen.sh /home/mock/gen.sh -RUN /home/mock/gen.sh 50 > /home/mock/example.txt &&\ +RUN chmod +x /home/mock/gen.sh && /home/mock/gen.sh 50 > /home/mock/example.txt &&\ echo '* * * * * /home/mock/gen.sh 10 >> /home/mock/example.txt' > /etc/crontabs/root CMD /usr/sbin/crond -l 2 -f \ No newline at end of file From 21b33e8a55213494a632f39b3b74da12013d49c5 Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 14 Jan 2022 17:44:30 +0100 Subject: [PATCH 207/226] Rework following logs --- .../types/LogViewerWidget/LogList/index.js | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 0b1b7f96e..3b79b2352 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -12,6 +12,8 @@ import { VariableGridSchema } from './styled'; +const logHeight = 28; + export default function LogList({ wid, logs, @@ -23,7 +25,11 @@ export default function LogList({ const theme = useTheme(); const virtuosoRef = useRef(null); const scrollerRef = useRef(null); - const listScrollPos = useRef(0); + const prevScrollPos = useRef(0); + const prevLastLogId = useRef(null); + const prevLogsLength = useRef(null); + + const logsCountOffset = logs.length - prevLogsLength.current; const VariableLogListHeader = () => ( @@ -34,20 +40,52 @@ export default function LogList({ ); const handleScroll = () => { - const isScrollingUpward = - listScrollPos.current > scrollerRef.current.scrollTop; + const scrollerOffset = + scrollerRef.current.scrollTop - prevScrollPos.current; + const isScrollingUpward = scrollerOffset < logHeight * logsCountOffset; if (isScrollingUpward) { handleFollowChange(false); } - listScrollPos.current = scrollerRef.current.scrollTop; + prevScrollPos.current = scrollerRef.current.scrollTop; + prevLogsLength.current = logs.length; }; useEffect(() => { if (shouldFollowLogs) { - virtuosoRef.current.scrollToIndex(logs.length - 1); + virtuosoRef.current.scrollToIndex({ + index: logs.length - 1, + align: 'bottom', + behavior: 'smooth' + }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [shouldFollowLogs]); + useEffect(() => { + if (!shouldFollowLogs) { + if (prevLastLogId.current !== null) { + let offset = 0; + for (let i = logs.length - 1; i >= 0; i--) { + if (logs[i]._id === prevLastLogId.current) { + break; + } + offset += 1; + } + + const COULD_NOT_FIND_LOG = offset == logs.length; + if (!COULD_NOT_FIND_LOG) { + offset -= logsCountOffset; + + virtuosoRef.current.scrollTo({ + top: prevScrollPos.current - offset * logHeight, + behavior: 'instant' + }); + } + } + } + prevLastLogId.current = logs.length > 0 && logs[logs.length - 1]._id; + }, [logs]); + const getLogByIndex = index => ( ); @@ -87,7 +125,7 @@ export default function LogList({ itemContent={getLogByIndex} atBottomThreshold={0} followOutput={isAtBottom => - shouldFollowLogs && !isAtBottom ? 'smooth' : false + shouldFollowLogs && !isAtBottom ? 'instant' : false } /> From c7a78004d34da3cc2e11b583bf374c8b1d6d0a1a Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 14 Jan 2022 19:38:27 +0100 Subject: [PATCH 208/226] Update following logs --- .../types/LogViewerWidget/LogList/LogEntry.js | 12 ++++------- .../types/LogViewerWidget/LogList/index.js | 12 ++++++----- .../AdvancedFiltersMenu/FilterForm.js | 12 +++++------ .../FilterPicker/AdvancedFiltersMenu/index.js | 14 ++++++------- .../Toolbar/FilterPicker/index.js | 3 +-- .../Toolbar/QuarantineModal/QuarantineForm.js | 15 ++++++++------ .../Toolbar/QuarantineModal/index.js | 20 +++++++++---------- .../types/LogViewerWidget/Toolbar/index.js | 2 -- .../widgets/types/LogViewerWidget/context.js | 4 +++- .../widgets/types/LogViewerWidget/index.js | 9 ++++----- 10 files changed, 51 insertions(+), 52 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index 1d22d3908..f4ebb8045 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -11,7 +11,7 @@ import { arrayOf, func } from 'prop-types'; -import { SimilarLogsContext } from '../context'; +import LogsViewerContext from '../context'; import { getGridTemplate, highlightText } from './helpers'; import { AccordionSummary, AccordionDetails, Tooltip } from '@material-ui/core'; @@ -31,7 +31,6 @@ import { import TextWithCopyButton from './TextWithCopyButton'; const LogEntry = ({ - wid, onToggle, id, type, @@ -43,7 +42,7 @@ const LogEntry = ({ }) => { const dispatch = useDispatch(); const isAuthenticated = useSelector(getIsAuthenticated); - const similarLogs = useContext(SimilarLogsContext); + const { wid, setFilter, setQuarantine } = useContext(LogsViewerContext); const expandedList = useSelector(store => store.widgets.logsViewersCache[wid]?.expandedLogs) || @@ -106,7 +105,7 @@ const LogEntry = ({ similarLogs.setFilter(getLastVariableHeader())} + onClick={() => setFilter(getLastVariableHeader())} > @@ -117,9 +116,7 @@ const LogEntry = ({ placement="left" > - similarLogs.setQuarantine(getLastVariableHeader()) - } + onClick={() => setQuarantine(getLastVariableHeader())} > @@ -136,7 +133,6 @@ const LogEntry = ({ export default LogEntry; LogEntry.propTypes = { - wid: string.isRequired, id: string.isRequired, type: string, date: string.isRequired, diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index 3b79b2352..d4ca2a1a0 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -15,7 +15,6 @@ import { const logHeight = 28; export default function LogList({ - wid, logs, template, search, @@ -42,7 +41,9 @@ export default function LogList({ const handleScroll = () => { const scrollerOffset = scrollerRef.current.scrollTop - prevScrollPos.current; - const isScrollingUpward = scrollerOffset < logHeight * logsCountOffset; + + const isScrollingUpward = + scrollerOffset < 0 && scrollerOffset < logHeight * logsCountOffset; if (isScrollingUpward) { handleFollowChange(false); } @@ -72,7 +73,7 @@ export default function LogList({ offset += 1; } - const COULD_NOT_FIND_LOG = offset == logs.length; + const COULD_NOT_FIND_LOG = offset === logs.length; if (!COULD_NOT_FIND_LOG) { offset -= logsCountOffset; @@ -84,6 +85,7 @@ export default function LogList({ } } prevLastLogId.current = logs.length > 0 && logs[logs.length - 1]._id; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [logs]); const getLogByIndex = index => ( @@ -92,7 +94,6 @@ export default function LogList({ const MemoLogEntry = React.memo(({ log }) => ( console.log('height', height)} itemContent={getLogByIndex} atBottomThreshold={0} followOutput={isAtBottom => - shouldFollowLogs && !isAtBottom ? 'instant' : false + shouldFollowLogs && !isAtBottom ? 'smooth' : false } /> diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js index 71ba23fad..ec06b8038 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/FilterForm.js @@ -6,7 +6,7 @@ import { Button } from '@material-ui/core'; import DynamicForm from '../../../../../../DynamicForm'; import { StyledCancelButton } from './styled'; import dialogFields from '../../../../../dialogFields'; -import { SimilarLogsContext } from '../../../context'; +import LogsViewerContext from '../../../context'; const FilterForm = ({ filters, @@ -16,15 +16,15 @@ const FilterForm = ({ filterSimilarLogsState, ...initialFormValues }) => { - const similarLogs = useContext(SimilarLogsContext); + const logsViewerContext = useContext(LogsViewerContext); useEffect(() => { - if (similarLogs.filter) { - setFieldValue(dialogFields.RegExpField.name, similarLogs.filter); - similarLogs.setFilter(null); + if (logsViewerContext.filter) { + setFieldValue(dialogFields.RegExpField.name, logsViewerContext.filter); + logsViewerContext.setFilter(null); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [similarLogs.filter]); + }, [logsViewerContext.filter]); const formFields = ['LabelField', 'RegExpField']; const constraints = { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js index 1d75e91ce..00afb2d6c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/FilterPicker/AdvancedFiltersMenu/index.js @@ -20,19 +20,19 @@ import EditFilter from './EditFilter'; import DeleteItem from '../../../../../../DeleteItem'; import FilterForm from './FilterForm'; import { StyledExitButton } from './styled'; -import { SimilarLogsContext } from '../../../context'; +import LogsViewerContext from '../../../context'; -const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { +const AdvancedFiltersMenu = ({ widgetLocalStorage, quarantine }) => { const [dialogOpened, openDialog, handleDialogClose] = useToggle(); - const similarLogs = useContext(SimilarLogsContext); + const logsViewerContect = useContext(LogsViewerContext); useEffect(() => { - if (similarLogs.filter || similarLogs.quarantine) { + if (logsViewerContect.filter || logsViewerContect.quarantine) { openDialog(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [similarLogs.filter, similarLogs.quarantine]); + }, [logsViewerContect.filter, logsViewerContect.quarantine]); const filters = getFilters(widgetLocalStorage); @@ -135,11 +135,11 @@ const AdvancedFiltersMenu = ({ widgetLocalStorage, wid, quarantine }) => { largeButton itemName="filter" submitAction={addFilter} - shouldOpen={similarLogs.filter} + shouldOpen={logsViewerContect.filter} > - + { +const FilterPicker = ({ widgetLocalStorage, quarantine }) => { const regExpFilters = getFilters(widgetLocalStorage); const logLevel = getLevel(widgetLocalStorage); @@ -102,7 +102,6 @@ const FilterPicker = ({ widgetLocalStorage, wid, quarantine }) => { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js index c6017d6cf..9f96bd741 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js @@ -6,7 +6,7 @@ import { Button, Tooltip, IconButton } from '@material-ui/core'; import InfoIcon from '@material-ui/icons/Info'; import { StyledHorizontalContainer, StyledCancelButton } from './styled'; import dialogFields from '../../../../dialogFields'; -import { SimilarLogsContext } from '../../context'; +import LogsViewerContext from '../../context'; import { URL } from '../../../../../../constants'; const QuarantineForm = ({ @@ -16,15 +16,18 @@ const QuarantineForm = ({ id, ...initialFormValues }) => { - const similarLogs = useContext(SimilarLogsContext); + const logsViewerContext = useContext(LogsViewerContext); useEffect(() => { - if (similarLogs.quarantine) { - setFieldValue(dialogFields.RegExpField.name, similarLogs.quarantine); - similarLogs.setQuarantine(null); + if (logsViewerContext.quarantine) { + setFieldValue( + dialogFields.RegExpField.name, + logsViewerContext.quarantine + ); + logsViewerContext.setQuarantine(null); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [similarLogs.quarantine]); + }, [logsViewerContext.quarantine]); const formFields = ['LabelField', 'RegExpField', 'ReasonField']; const constraints = { diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js index 4bb1b3a6c..dee5f21df 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/index.js @@ -18,20 +18,20 @@ import AddItem from '../../../../../AddItem'; import QuarantineForm from './QuarantineForm'; import EditQFilter from './EditQFilter'; import DeleteItem from '../../../../../DeleteItem'; -import { SimilarLogsContext } from '../../context'; +import LogsViewerContext from '../../context'; -const QuarantineModal = ({ wid, quarantine }) => { +const QuarantineModal = ({ quarantine }) => { const isAuthenticated = useSelector(getIsAuthenticated); const [dialogOpened, openDialog, handleDialogClose] = useToggle(); - const similarLogs = useContext(SimilarLogsContext); + const logsViewerContext = useContext(LogsViewerContext); useEffect(() => { - if (similarLogs.quarantine) { + if (logsViewerContext.quarantine) { openDialog(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [similarLogs.quarantine]); + }, [logsViewerContext.quarantine]); const handleQuarantineClick = event => { event.stopPropagation(); @@ -40,14 +40,14 @@ const QuarantineModal = ({ wid, quarantine }) => { const addFilter = values => { postWidgetContentUpdate({ - id: wid, + id: logsViewerContext.wid, quarantineRules: [...quarantine, { id: v4(), checked: true, ...values }] }); }; const editFilter = ({ id, values }) => { postWidgetContentUpdate({ - id: wid, + id: logsViewerContext.wid, quarantineRules: quarantine.map(filter => { if (filter.id === id) { return { id, ...values }; @@ -59,7 +59,7 @@ const QuarantineModal = ({ wid, quarantine }) => { const handleSwitchChange = id => { postWidgetContentUpdate({ - id: wid, + id: logsViewerContext.wid, quarantineRules: quarantine.map(filter => filter.id === id ? { ...filter, checked: !filter.checked } : filter ) @@ -68,7 +68,7 @@ const QuarantineModal = ({ wid, quarantine }) => { const deleteAction = id => { postWidgetContentUpdate({ - id: wid, + id: logsViewerContext.wid, quarantineRules: quarantine.filter(quarantine => quarantine.id !== id) }); }; @@ -135,7 +135,7 @@ const QuarantineModal = ({ wid, quarantine }) => { largeButton itemName="quarantine" submitAction={addFilter} - shouldOpen={similarLogs.quarantine} + shouldOpen={logsViewerContext.quarantine} > diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index 63a7ff0a9..cdd441cb4 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -10,7 +10,6 @@ import FilterPicker from './FilterPicker'; import { saveFile } from './helpers'; const Toolbar = ({ - wid, quarantine, widgetLocalStorage, setSearchFilter, @@ -28,7 +27,6 @@ const Toolbar = ({ /> { return ( - { }} > { logs={filteredLogs} /> - + ); }; From ed0c018a6d186ebe79a9351a93db788b8f0691c4 Mon Sep 17 00:00:00 2001 From: clmrv Date: Fri, 14 Jan 2022 20:27:29 +0100 Subject: [PATCH 209/226] Fix following when list is not full --- .../widgets/types/LogViewerWidget/LogList/index.js | 6 +++--- .../src/components/widgets/types/LogViewerWidget/index.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js index d4ca2a1a0..5741d4427 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/index.js @@ -19,7 +19,8 @@ export default function LogList({ template, search, shouldFollowLogs, - handleFollowChange + handleFollowChange, + logListFull }) { const theme = useTheme(); const virtuosoRef = useRef(null); @@ -63,7 +64,7 @@ export default function LogList({ }, [shouldFollowLogs]); useEffect(() => { - if (!shouldFollowLogs) { + if (!shouldFollowLogs && logListFull) { if (prevLastLogId.current !== null) { let offset = 0; for (let i = logs.length - 1; i >= 0; i--) { @@ -123,7 +124,6 @@ export default function LogList({ isScrolling={handleScroll} totalCount={logs.length} increaseViewportBy={300} // defines loading overlap (in pixels) - totalListHeightChanged={height => console.log('height', height)} itemContent={getLogByIndex} atBottomThreshold={0} followOutput={isAtBottom => diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index 5d97c2b17..be8d4f848 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -82,6 +82,7 @@ const LogViewerWidget = ({ id }) => { /> Date: Fri, 14 Jan 2022 20:54:34 +0100 Subject: [PATCH 210/226] Remove logs from toolbar --- .../types/LogViewerWidget/Toolbar/helpers.js | 18 --------------- .../types/LogViewerWidget/Toolbar/index.js | 5 ++--- .../widgets/types/LogViewerWidget/helpers.js | 22 +++++++++++++++++++ .../widgets/types/LogViewerWidget/index.js | 14 ++++++------ 4 files changed, 31 insertions(+), 28 deletions(-) delete mode 100644 cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/helpers.js diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/helpers.js deleted file mode 100644 index f29651524..000000000 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/helpers.js +++ /dev/null @@ -1,18 +0,0 @@ -export const saveFile = blob => { - const a = document.createElement('a'); - const today = new Date(); - const options = { - weekday: 'long', - year: 'numeric', - month: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }; - a.download = today.toLocaleDateString('en-US', options) + '.txt'; - a.href = URL.createObjectURL(blob); - a.addEventListener('click', e => { - setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000); - }); - a.click(); -}; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js index cdd441cb4..9a3426ac1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/index.js @@ -7,7 +7,6 @@ import DateRangePicker from './DateRangePicker'; import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; import GetAppIcon from '@material-ui/icons/GetApp'; import FilterPicker from './FilterPicker'; -import { saveFile } from './helpers'; const Toolbar = ({ quarantine, @@ -16,7 +15,7 @@ const Toolbar = ({ shouldFollowLogs, handleFollowChange, lastLog, - logs + onSaveLogs }) => { return ( @@ -42,7 +41,7 @@ const Toolbar = ({ saveFile(new Blob([JSON.stringify(logs)]))} + onClick={onSaveLogs} /> ); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js index 8471f43a3..c1d77622b 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js @@ -7,3 +7,25 @@ export const joinLogs = (currentLogs, newLogs, logLines) => { }); return joined.slice(-logLines); }; + +const saveFile = blob => { + const a = document.createElement('a'); + const today = new Date(); + const options = { + weekday: 'long', + year: 'numeric', + month: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }; + a.download = today.toLocaleDateString('en-US', options) + '.txt'; + a.href = URL.createObjectURL(blob); + a.addEventListener('click', e => { + setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000); + }); + a.click(); +}; + +export const saveLogsToFile = logs => + saveFile(new Blob([JSON.stringify(logs)])); diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js index be8d4f848..8b9cabcc6 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/index.js @@ -2,13 +2,9 @@ import React, { useEffect, useState } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import { number, string } from 'prop-types'; import { useLocalStorage } from '../../../../hooks'; - -import Toolbar from './Toolbar'; -import LogList from './LogList'; -import { Container } from './styled'; -import { getInitialLogs } from '../../../../utils/fetch'; -import { joinLogs } from './helpers'; +import { joinLogs, saveLogsToFile } from './helpers'; import LogsViewerContext from './context'; +import { getInitialLogs } from '../../../../utils/fetch'; import { getFilters, getLevel } from './Toolbar/FilterPicker/helpers'; import { getDateSpan } from './Toolbar/DateRangePicker/helpers'; import { @@ -17,6 +13,10 @@ import { filterByLevel } from './LogList/helpers'; +import Toolbar from './Toolbar'; +import LogList from './LogList'; +import { Container } from './styled'; + const LogViewerWidget = ({ id }) => { const widgetData = useSelector( ({ widgets }) => widgets.widgetsById[id], @@ -78,7 +78,7 @@ const LogViewerWidget = ({ id }) => { shouldFollowLogs={shouldFollowLogs} handleFollowChange={setFollow} lastLog={storedLogs?.length > 0 && storedLogs[storedLogs.length - 1]} - logs={filteredLogs} + onSaveLogs={() => saveLogsToFile(filteredLogs)} /> Date: Sat, 15 Jan 2022 13:23:18 +0100 Subject: [PATCH 211/226] Change let to const --- .../src/components/widgets/types/LogViewerWidget/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js index fe3c466cb..381ab8e67 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/helpers.js @@ -1,5 +1,5 @@ export const joinLogs = (currentLogs, newLogs, logLines) => { - let joined = currentLogs.slice(); + const joined = currentLogs.slice(); newLogs.forEach(newLog => { if (!joined.some(log => log._id === newLog._id)) { joined.push(newLog); From 77b36f8b6ca0cff3bc6cb499904b23332bc3d88d Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 15 Jan 2022 15:12:54 +0100 Subject: [PATCH 212/226] Fix highlight test --- .../cypress/integration/logsViewer.js | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/functional/cypress-tests/cypress/integration/logsViewer.js b/functional/cypress-tests/cypress/integration/logsViewer.js index f61c1c6fb..9d62c158e 100644 --- a/functional/cypress-tests/cypress/integration/logsViewer.js +++ b/functional/cypress-tests/cypress/integration/logsViewer.js @@ -10,7 +10,7 @@ import { submitForm, assertChip, logsMatchLogLevel, - selectLogLevel + selectLogLevel, } from '../support/logsViewer/filters'; import { filters, logLevels } from '../fixtures/logsViewer'; @@ -29,7 +29,7 @@ describe('Logs Viewer', () => { cy.clickAddWidgetButton(); widget = createWidget(logsViewer.name).configure(false, { cols: 8, - rows: 2 + rows: 2, }); }); @@ -102,7 +102,7 @@ describe('Logs Viewer', () => { it('should delete filters', () => { openAdvancedMenu(); - cy.get('[data-cy="delete-filter-delete-button"]').each(filter => { + cy.get('[data-cy="delete-filter-delete-button"]').each((filter) => { cy.wrap(filter).click(); cy.get('[data-cy="confirmation-dialog-ok"]').click(); }); @@ -119,7 +119,7 @@ describe('Logs Viewer', () => { }); describe('Log level', () => { - logLevels.forEach(selectedLevel => { + logLevels.forEach((selectedLevel) => { it(`show logs with greater or equal level to ${selectedLevel.value}`, () => { selectLogLevel(selectedLevel.value); logsMatchLogLevel(selectedLevel, logLevels); @@ -172,11 +172,11 @@ describe('Logs Viewer', () => { describe('Searchbar', () => { it('shows search icon while there are less than 3 letters in input', () => { cy.get('[data-cy="search-icon"]').should('exist'); - cy.get('[data-cy="search-input-field"]').type('l'); + cy.get('[data-cy="search-input-field"]').type('m'); cy.get('[data-cy="search-icon"]').should('exist'); - cy.get('[data-cy="search-input-field"]').type('o'); + cy.get('[data-cy="search-input-field"]').type('e'); cy.get('[data-cy="search-icon"]').should('exist'); - cy.get('[data-cy="search-input-field"]').type('r'); + cy.get('[data-cy="search-input-field"]').type('s'); cy.get('[data-cy="search-icon"]').should('not.exist'); }); @@ -185,19 +185,17 @@ describe('Logs Viewer', () => { }); it('shows highlight mark on logs fitting expression', () => { - cy.get('[data-cy="highlight-mark"]').each(mark => { + cy.get('[data-cy="highlight-mark"]').each((mark) => { cy.wrap(mark) .closest('[data-cy="log-entry"]') - .contains(new RegExp('lor', 'gi')) + .contains('[data-cy="log-variable-data"] p', new RegExp('mes', 'gi')) .should('exist'); }); }); it('clears input after clicking close icon', () => { cy.get('[data-cy="close-icon"]').click(); - cy.get('[data-cy="search-input-field"]') - .invoke('val') - .should('be.empty'); + cy.get('[data-cy="search-input-field"]').invoke('val').should('be.empty'); }); }); }); From 661672a4097ddfd5d2028257f3f3e8bdf136d011 Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 15 Jan 2022 16:31:52 +0100 Subject: [PATCH 213/226] Revoke unmountOnExit --- .../widgets/types/LogViewerWidget/LogList/LogEntry.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js index f4ebb8045..8b7049c55 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/LogEntry.js @@ -80,12 +80,7 @@ const LogEntry = ({ return ( - + } From 7cbf63fe2b8e919110d23c62eef46474a38e7c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Sun, 16 Jan 2022 15:17:16 +0100 Subject: [PATCH 214/226] Change some details in the UI --- .../Toolbar/QuarantineModal/QuarantineForm.js | 30 +++++++++++-------- .../Toolbar/QuarantineModal/styled.js | 23 ++++++++++---- .../Toolbar/SearchInput/index.js | 2 +- .../types/LogViewerWidget/Toolbar/index.js | 4 +-- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js index 2030f4b30..cfeca0676 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/QuarantineForm.js @@ -4,10 +4,11 @@ import { useFormData } from '../../../../../../hooks'; import DynamicForm from '../../../../../DynamicForm'; import { Button, Tooltip, IconButton } from '@material-ui/core'; import InfoIcon from '@material-ui/icons/Info'; -import { StyledHorizontalContainer, StyledCancelButton } from './styled'; +import { StyledHorizontalContainer, StyledButtonContainer } from './styled'; import dialogFields from '../../../../dialogFields'; import LogsViewerContext from '../../context'; import { URL } from '../../../../../../constants'; +import CancelButton from '../../../../../CancelButton'; const QuarantineForm = ({ filters, @@ -79,18 +80,21 @@ const QuarantineForm = ({ errors={errors} rootName="quarantine-form" /> - - + + + +

In order to see the changes, you have to refresh the page.

+
); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js index c1e7d1bf3..95a80e5e7 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/QuarantineModal/styled.js @@ -1,18 +1,31 @@ import styled from '@emotion/styled/macro'; import { Button } from '@material-ui/core'; -import CancelButton from '../../../../../CancelButton'; export const StyledButton = styled(Button)` margin-top: 12px; `; -export const StyledCancelButton = styled(CancelButton)` - margin-left: 20px; -`; - export const StyledHorizontalContainer = styled.div` display: flex; align-items: center; gap: 12px; `; + +export const StyledButtonContainer = styled.div` + display: grid; + grid-template: auto / auto auto 1fr; + align-items: center; + gap: 20px; + + p { + margin: 0; + } + + @media (max-width: 764px) { + grid-template: auto auto / repeat(2, min-content) auto; + p { + grid-column: 1 / 4; + } + } +`; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js index 4db10ac5c..d5ccc777c 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/Toolbar/SearchInput/index.js @@ -26,7 +26,7 @@ const SearchInput = ({ setSearchFilter, debounce, minLetters }) => { return ( From c972c3306e7d20700b4f9d62aa7088f5dcf915ed Mon Sep 17 00:00:00 2001 From: "szymon.owczarzak" Date: Mon, 17 Jan 2022 11:10:17 +0100 Subject: [PATCH 215/226] Moving gen.sh script into Dockerfile --- ssh/Dockerfile | 27 ++++++++++++++++++++++++--- ssh/gen.sh | 20 -------------------- 2 files changed, 24 insertions(+), 23 deletions(-) delete mode 100755 ssh/gen.sh diff --git a/ssh/Dockerfile b/ssh/Dockerfile index 18e01e152..515fa1c9b 100644 --- a/ssh/Dockerfile +++ b/ssh/Dockerfile @@ -5,7 +5,28 @@ ENV PASSWORD_ACCESS=true ENV USER_NAME=mock ENV USER_PASSWORD=TLQuoLMn*T89&Y*r*YqHviSFH6MkR!4E -ADD gen.sh /home/mock/gen.sh -RUN chmod +x /home/mock/gen.sh && /home/mock/gen.sh 50 > /home/mock/example.txt &&\ - echo '* * * * * /home/mock/gen.sh 10 >> /home/mock/example.txt' > /etc/crontabs/root +RUN mkdir /home/mock +RUN echo $'#!/bin/bash\n\ +labels=(DEBUG INFO WARNING ERROR)\n\ +words=(Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sit amet massa sit amet mi feugiat lobortis. Morbi ultrices hendrerit luctus. Donec fermentum viverra viverra. Integer convallis sapien sit amet facilisis pretium. Ut quis commodo odio, ac bibendum urna. Ut imperdiet ante sed ex sollicitudin auctor. Nulla vel eros sit amet velit vulputate suscipit a malesuada felis. Curabitur commodo, erat eget condimentum tristique, ante diam porttitor nulla, condimentum vulputate nibh ex sit amet velit. Vestibulum vel lectus bibendum, pellentesque nisl id, vehicula tellus. Suspendisse sem turpis, dignissim quis aliquet nec, laoreet sit amet urna. Nulla non euismod tellus, id varius)\n\ +\n\ +COUNT=5\n\ +if ! [ -z "$1" ]; then\n\ + COUNT=$1\n\ +fi\n\ +\n\ +for run in $( seq 1 $COUNT ); do\n\ + d=$(date +%Y-%m-%d:%H:%M:%S)\n\ + l=${labels[$(($RANDOM%4))]}\n\ + w=()\n\ + wCount=$(($RANDOM%5+5))\n\ + for (( i=0; i<$wCount; i++ ))\n\ + do\n\ + w[i]=${words[$(($RANDOM%${#words[@]}))]}\n\ + done\n\ + echo $d "*"${l}"* [FelixStartLevel] " ${w[*]}\n\ +done' > /home/mock/gen.sh +RUN chmod +x /home/mock/gen.sh +RUN /home/mock/gen.sh 50 > /home/mock/example.txt +RUN echo '* * * * * /home/mock/gen.sh 10 >> /home/mock/example.txt' > /etc/crontabs/root CMD /usr/sbin/crond -l 2 -f \ No newline at end of file diff --git a/ssh/gen.sh b/ssh/gen.sh deleted file mode 100755 index b4b511bf7..000000000 --- a/ssh/gen.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -labels=(DEBUG INFO WARNING ERROR) -words=(Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sit amet massa sit amet mi feugiat lobortis. Morbi ultrices hendrerit luctus. Donec fermentum viverra viverra. Integer convallis sapien sit amet facilisis pretium. Ut quis commodo odio, ac bibendum urna. Ut imperdiet ante sed ex sollicitudin auctor. Nulla vel eros sit amet velit vulputate suscipit a malesuada felis. Curabitur commodo, erat eget condimentum tristique, ante diam porttitor nulla, condimentum vulputate nibh ex sit amet velit. Vestibulum vel lectus bibendum, pellentesque nisl id, vehicula tellus. Suspendisse sem turpis, dignissim quis aliquet nec, laoreet sit amet urna. Nulla non euismod tellus, id varius) - -COUNT=5 -if ! [ -z "$1" ]; then - COUNT=$1 -fi - -for run in $( seq 1 $COUNT ); do - d=$(date +%Y-%m-%d:%H:%M:%S) - l=${labels[$(($RANDOM%4))]} - w=() - wCount=$(($RANDOM%5+5)) - for (( i=0; i<$wCount; i++ )) - do - w[i]=${words[$(($RANDOM%${#words[@]}))]} - done - echo $d "*"${l}"* [FelixStartLevel] " ${w[*]} -done \ No newline at end of file From c734ca9abfb463b0e57b0bf422d248c001d684fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Mon, 17 Jan 2022 20:04:13 +0100 Subject: [PATCH 216/226] Documented LogStorage class #448 --- .../widget/type/logviewer/LogsViewer.md | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md index 9e01b7efa..858d3e9fb 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md @@ -40,7 +40,71 @@ delivered by `ConnectionStrategy` passed in constructor. Returns regexes of quarantine rules that are enabled at the moment. -- `` +- `logsCollection: MongoCollection` + +gets collection of logs associated to this widget from mongoDB + +- `collectionState: LogCollectionState?` + +Returns a logs collection configuration associated with this widget (if present). #### Methods -- `` \ No newline at end of file +- `saveState(state: LogCollectionState?)` + +Saves new state of widget or deletes it from database if no state is passed as argument + +- `deleteAllLogs()` + +Deletes all logs from collection associated with given widget. + +- `deleteFirstLogs(n: Long)` + +Deletes given number of logs starting from lowest `seq` value in collection. +Used to keep configured number of logs in the database + +- `deleteOldLogs()` + +Deletes logs that have been in the database for longer than the configured validity time. + +- `deleteSpaceConsumingLogs()` + +Computes how many logs have to be deleted to keep collection size under configured max value. +Then uses `deleteFirstLogs` method to get rid of them. + +- `filter(logs: MutableList)` + +Deletes logs meeting the quarantine criteria from list of newly downloaded entries. + +- `filterExistingLogs()` + +Deletes entries from collection that meet the quarantine criteria. + +- `downloadFilterLogs(skipFirstLines: Long? = null): List` + +Downloads new logs from remote server, skipping number of lines passed as a parameter - in our use case it is +the number of the last downloaded line in the previous connection to the remote. Received logs are also +filtered using `filter` method. + +- `insertLogs(seq: Long, logs: Collection)` + +Used to save received `logs` to collection associated with current widget instance. + +`seq` is used to index logs in database in order of their appearance in remote log file. + +- `downloadLogs(): List` + +Checks collection state for number of last line downloaded and last `seq` value assigned. +Then logs are downloaded using `downloadFilterLogs` method and added to database by calling `insertLogs`. + +- `prepareResponse(insertedLogs: List): JsonObject` + +Prepares logs for sending them to frontend and adds `variableFields` from parser to give frontend information +how to display the logs according to how they were parsed. + +- `updateLogs(fetchNewLogs: Boolean)` + +Downloads new logs if `fetchNewLogs` is set to `true` and sends data returned by `prepareResponse` method to frontend + +- `delete()` + +Deletes all data associated with used widget instance. \ No newline at end of file From ad4792718009681aee2066ef13e28d321a2cea3d Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 17 Jan 2022 23:51:49 +0100 Subject: [PATCH 217/226] Add info about log list scroll --- .../types/LogViewerWidget/LogsViewer.md | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index e02e20afe..b8a0ebe3e 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -27,24 +27,40 @@ Dates (begin and end) are held in state as `momentjs` objects. Each time value c Log dates are strings. -### Follow logs - -_TODO_ - ### Clear logs Hides all logs that have been delivered before usage. Sets begin date of `DateRangePicker` to arrival date of last log. -### Quarantine - -_TODO_ - ## LogList Displays logs and column names. Will not render if logs are not provided (template required). +Loglist flow is straight forward untill count of logs meets maximum number of logs. Then new logs are pushing old ones upward and you have to manually compensate to prevent screen shaking. Component remembers last: + +- `prevScrollPos` - previous scroll position +- `prevLastLogId` - previous last log on the list +- `prevLogsLength` - previous logs length + +### On logs change: + +It finds where is lastLog after adding new logs (offset). +It subtracts new logs count from offset because +Offset != newLogsCount. +Depending on filters, there might be less logs after adding new logs - old logs mathed filter, new ones don't). +Then it updates last log id (it won't update logsCount, because this function will call onScroll and it needs old logsCount value). + +### On scroll: + +- `scrollerOffset` < 0 + + it moved upward + +- `scrollerOffset` < `logHeight` \* `logsCountOffset` + + it moved more than moving from 'on logs change' could + ### VariableGridSchema Component which provides equal columns widths for `Header` and `LogEntry`. @@ -53,7 +69,7 @@ Component which provides equal columns widths for `Header` and `LogEntry`. ### Logs -Virtuoso is used to virtualize list. +Virtuoso is used to virtualize the list. ### Highlighting From 21454ed870565f702667fa812f472ed9786266a1 Mon Sep 17 00:00:00 2001 From: clmrv Date: Mon, 17 Jan 2022 23:57:41 +0100 Subject: [PATCH 218/226] Add info about following logs --- .../src/components/widgets/types/LogViewerWidget/LogsViewer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index b8a0ebe3e..1db5ef4d3 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -77,7 +77,7 @@ There is double check for highlight - one for marker in the left-upper corner (` ### Following logs -_TODO_ +Virtuoso handles scrolling. It is triggered only when logs.length changes. When log limit isn't met it works, when it is it doesn't need to work. There is manual scroll to the last log on following start. ### LogEntry From 5a8c55ac7bc4ecfcf84e1ddd2a01b396c6397d82 Mon Sep 17 00:00:00 2001 From: clmrv Date: Tue, 18 Jan 2022 00:03:15 +0100 Subject: [PATCH 219/226] Add copying info --- .../components/widgets/types/LogViewerWidget/LogsViewer.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index 1db5ef4d3..c14e4b586 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -69,7 +69,7 @@ Component which provides equal columns widths for `Header` and `LogEntry`. ### Logs -Virtuoso is used to virtualize the list. +Virtuoso is used to virtualize the list. (https://virtuoso.dev) ### Highlighting @@ -82,3 +82,7 @@ Virtuoso handles scrolling. It is triggered only when logs.length changes. When ### LogEntry Every log column consists of `header` (accordion summary) and `description` (accordion details). + +Outer container cannot have margins (wrong virtuoso calculations). + +Copying feature doesn't work on servers without HTTPS. From c2da0355c80a6f5347082102294e0e0d364fd964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Tue, 18 Jan 2022 01:20:05 +0100 Subject: [PATCH 220/226] Documented LogParsing and Connection strategies with factories #448 --- .../widget/type/logviewer/LogsViewer.md | 89 ++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md index 858d3e9fb..b15916571 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md @@ -107,4 +107,91 @@ Downloads new logs if `fetchNewLogs` is set to `true` and sends data returned by - `delete()` -Deletes all data associated with used widget instance. \ No newline at end of file +Deletes all data associated with used widget instance. + +### LogParserStrategy +Interface for building new parsers of logs received from the server. +#### Fields +- `variableFields: List` + +Stores list of fields that are parsed out of log line, different from required fields of `date` and `type` + +#### Methods +- `parseLine(line: String): Log` + +Transforms line from log file to instance of `Log` class. This method should capture data for mandatory fields: +`date` and `type`, but also for fields stored in `variableFields` list. + +### LogParserStrategyFactory +Factory class used to create instances of classes implementing `LogParserStrategy` +#### Inner class +- `enum class Type` + +Used to distinguish what type of parser should be created. When implementing new parser, new entry should be +added to this enumerator. + +#### Methods +- `build(typeStr: String): LogParserStrategy` + +Creates instance of parser, based on the passed `typeStr`. Passed parameter value should match name of any entry +in `Type` enumerator. If value doesn't match, `UnknownParserTypeException` is thrown. + +### ConnectionStrategy +Abstract class used to connect to the server and retrieve new lines of logs or number of lines in remote log file. +By using references to this class, rest of the application doesn't have to know what method is used for +obtaining the data. + +#### Methods +- `authenticationTypes(): Set` + +Method should return types of authentication that can be used by this strategy. Values returned should be entries +of enum classes such as `AuthenticationType` from package `com.cognifide.cogboard.http.auth` + +- `getNumberOfLines(): Long?` + +Implementations should fetch information from remote server about amount of lines in monitored log file. + +- `getLogs(skipFirstLines: Long?): Collection` + +Implementations should download logs from monitored file, optionally skipping given number of first lines e.g. +if log file has 200 lines and `skipFirstLines` is set to 50, only lines 51 to 200 should be downloaded. + +### SSHConnectionStrategy +Implementation of `ConnectionStrategy` class. Uses SSH protocol to connect to server. +#### Overriden methods +- `authenticationTypes(): Set` + +This strategy allows two types of authentication: + +`BASIC` - username + password + +`SSH_KEY` - username + private SSH key + +- `getNumberOfLines(): Long?` + +Bash command `wc` is executed on the server by instance of `SSHClient` class + +- `getLogs(skipFirstLines: Long?): Collection` + +Bash command `tail` or `cat` is executed on the server by `SSHClient` instance, depending on whether non-null +value was passed in parameter `skipFirstLines`. + +#### Other methods +- `prepareConfig(config: JsonObject): JsonObject` + +Ensures that required fields are added to `config` JsonObject. Calls `prepareConfigLines` method. + +- `prepareConfigLines(config: JsonObject, vararg fields: String): JsonObject` + +Iterates over values passed as `fields` and checks if field of that name is present in `config` object - if not +then it's taken from `endpoint.loaded` field, which stores info about remote that widget connects to. + +### ConnectionStrategyFactory +Factory class that creates needed `ConnectionStrategy` implementation based on `address` passed in constructor. + +#### Methods +- `build(): ConnectionStrategy` + +Returns created `ConnectionStrategy` implementation which is created based on `scheme` from instance of `URI` +class created by passing `address` to its static `create` method. Upon adding support for new protocol of +communication, value of `scheme` for that protocol should be added to this method's `when` expression \ No newline at end of file From f02ac2645ba032be1d694f03da20cf270591dcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 18 Jan 2022 22:17:58 +0100 Subject: [PATCH 221/226] Add mongo-data volume --- cogboard-local-compose.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 18b44b327..5b2ab9ac6 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -5,6 +5,9 @@ networks: driver: overlay attachable: true +volumes: + mongo-data: + services: api-mocks: image: rodolpheche/wiremock @@ -45,7 +48,7 @@ services: MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:-root} MONGO_INITDB_DATABASE: "logs" volumes: - - "./mnt/mongo:/data/db" + - "mongo-data:/data/db" networks: - cognet From 1609ce027008f65b48451d5b3c2e74704fef2e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ambroz=CC=87y?= Date: Tue, 18 Jan 2022 23:43:44 +0100 Subject: [PATCH 222/226] Change container name --- cogboard-local-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-local-compose.yml b/cogboard-local-compose.yml index 5b2ab9ac6..c5efd52ef 100644 --- a/cogboard-local-compose.yml +++ b/cogboard-local-compose.yml @@ -60,7 +60,7 @@ services: # environment: # ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_USER:-root} # ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD:-root} - # ME_CONFIG_MONGODB_URL: "mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/" + # ME_CONFIG_MONGODB_URL: "mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo-logs-storage:27017/" # networks: # - cognet From 20d8a1f066d48c41e1ae4c0f58b21f3a290d5b15 Mon Sep 17 00:00:00 2001 From: clmrv Date: Thu, 20 Jan 2022 01:57:11 +0100 Subject: [PATCH 223/226] Add info about storedLogs --- .../components/widgets/types/LogViewerWidget/LogsViewer.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index c14e4b586..d41eddbf1 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -4,6 +4,11 @@ - `searchFilter` - state of searchbox highlight (not to be confused with state of searchbox input) - `template` - log template (array of column names, does not include `type` and `date`) +## index + +- `newLogs` - logs delivered through knotx, new every iteration +- `storedLogs` - all logs in app; first iteration of logs is delivered through rest api, then appends logs from `newLogs` + ## Toolbar ### SearchInput From b7a899095b4716739aedf547df23e49c28000b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Przypa=C5=9Bniak?= Date: Thu, 20 Jan 2022 02:52:54 +0100 Subject: [PATCH 224/226] Documented rest of the application back-end #448 --- .../cogboard/ssh/auth/SSHAuthData.kt | 1 - .../widget/type/logviewer/LogsViewer.md | 150 +++++++++++++++++- 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt index e39707230..35bcc26e9 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/ssh/auth/SSHAuthData.kt @@ -12,7 +12,6 @@ class SSHAuthData(config: JsonObject) { private val id = config.getString(Props.ID, "") val user: String = config.getString(Props.USER, "") val password: String = config.getString(Props.PASSWORD, "") - val token: String = config.getString(Props.TOKEN, "") var key = config.getString(Props.SSH_KEY, "") private set val host: String diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md index b15916571..7d723cac6 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/logviewer/LogsViewer.md @@ -194,4 +194,152 @@ Factory class that creates needed `ConnectionStrategy` implementation based on ` Returns created `ConnectionStrategy` implementation which is created based on `scheme` from instance of `URI` class created by passing `address` to its static `create` method. Upon adding support for new protocol of -communication, value of `scheme` for that protocol should be added to this method's `when` expression \ No newline at end of file +communication, value of `scheme` for that protocol should be added to this method's `when` expression + +### SSHClient +Class responsible for connecting and executing commands on remote server via SSH + +#### Fields +- session: Session? + +Field for storing object representing SSH session established between server and remote containing the logs. + +- jsch: JSch? + +Field storing instance of main class for using `JSch` library, managing SSH connection. + +#### Methods +- openSession() + +Populates fields `session` and `jsch` with proper data by creating `JSch` instance and delegating establishing of +`Session` to proper `SessionStrategy`. + +- closeSession() + +Closes connection to remote server and removes references to objects from `session` and `jsch` fields + +- execute(command: String): String? + +Executes bash command on remote server, passed in `command` parameter. + +- executeAndClose(command: String): String? + +Calls `execute` method and closes session via call to `closeSession` + +- createChannel(command: String): Pair? + +Opens channel used for executing commands on remote server. After execution of command it returns created +`ChannelExec` object and `InputStream` later used for reading the response. + +- readResponse(stream: InputStream): String? + +Reads from `stream` and returns the response. + +### SessionStrategy +Abstract class used for proper authentication via SSH, depending on available user data. + +#### Fields +- jsch: JSch + +Instance `JSch` managing current SSH connection. + +- authData: SSHAuthData + +Container with data used for authentication on the remote server. + +- securityString: String + +Retrieves string used for authentication, such as password or SSH key, from `authData` + +#### Methods +- initSession(): Session + +Method used to properly authenticate via SSH on the remote. + +### BasicAuthSessionStrategy +Authenticates on the server using username and password. + +### SSHKeyAuthSessionStrategy +Authenticates on the server using username and SSH key. + +### SSHAuthData +Class used to retrieve and store data later used to authenticate on the server. + +#### Fields +- id: String + +Id of the widget connecting to the server via SSH + +- user: String + +Username used to authenticate on the server. + +- password: String + +Password to authenticate on the server. Might be an empty string. + +- key: String + +SSH key used to authorize on the server. Might be an empty string. + +- host: String + +Hostname to connect to via SSH + +- port: Int + +Port to connect to via SSH + +- authenticationType: AuthenticationType + +Determines whether connection will be authenticated via username and password or via SSH key + +#### Methods +- fromConfigAuthenticationType(config: JsonObject): AuthenticationType + +Returns `AuthenticationType` value based on `authenticationTypes` field in config + +- hasAuthTypeCorrectCredentials(authType: AuthenticationType): Boolean + +Checks whether proper credentials were provided for chosen `AuthenticationType` + +- prepareForSSHKeyUsage() + +Saves SSH key from endpoint credentials to file to be used by Jsch library. + +- getAuthenticationString(): String + +Returns proper string to be used for authentication alongside username. + +### SSHKeyFileHelper +Class used for managing the process of saving SSH key from property in JsonObject + +#### Fields +- id: String + +Id of widget establishing the connection. + +- key: String + +SSH key to be saved to file + +- file: File + +Object representing file to which SSH key will be saved + +- path: String + +Path to the file with SSH key + +#### Methods +- saveToFile() + +Method which saves the SSH key to the file + +- getOrCreateFile() + +Creates of fetches the reference to the file to which the SSH key will be saved + +- determineFilepath() + +Determines where to look for the file containing SSH key \ No newline at end of file From a2a534f47d0b844767c4214529be72ab5bfb0c8d Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 22 Jan 2022 18:24:57 +0100 Subject: [PATCH 225/226] Change copy method from navigator to execCommand --- .../LogList/TextWithCopyButton.js | 30 +++++++++++++++---- .../types/LogViewerWidget/LogList/styled.js | 1 + 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js index e4766ca6e..1569a4691 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/TextWithCopyButton.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import FileCopyIcon from '@material-ui/icons/FileCopy'; import { StyledCopyButton, Text, TextWithCopyButtonContainer } from './styled'; @@ -6,19 +6,35 @@ import { Tooltip } from '@material-ui/core'; const tooltipMessages = { standard: 'Copy to clipboard', - copied: 'Copied!' + copied: 'Copied!', + error: 'Error: could not copy' }; const TextWithCopyButton = ({ text, ...props }) => { const [tooltipMsg, setTooltipMsg] = useState(tooltipMessages.standard); + const textRef = useRef(null); const setStandardTooltipMsg = () => setTimeout(() => setTooltipMsg(tooltipMessages.standard), 200); - const setCopiedTooltipMsg = () => setTooltipMsg(tooltipMessages.copied); + const setErrorTooltipMsg = () => setTooltipMsg(tooltipMessages.error); + + const handleCopy = () => { + try { + const selection = window.getSelection(); + selection.removeAllRanges(); + + const range = document.createRange(); + range.selectNodeContents(textRef.current); - const handleCopy = () => - navigator.clipboard.writeText(text).then(() => setCopiedTooltipMsg()); + selection.addRange(range); + document.execCommand('copy'); + selection.removeAllRanges(); + setCopiedTooltipMsg(); + } catch (err) { + setErrorTooltipMsg(); + } + }; return ( @@ -33,7 +49,9 @@ const TextWithCopyButton = ({ text, ...props }) => {
- {text} + + {text} + ); }; diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js index d4dea3e60..173e77112 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogList/styled.js @@ -50,6 +50,7 @@ export const Text = styled(Typography)(({ type }) => { } return ` + user-select: auto; line-height: 19px; font-size: 0.8rem; font-weight: 400; From 4e9f7c041679a447eaa961c53a35172b4ccf29ef Mon Sep 17 00:00:00 2001 From: clmrv Date: Sat, 22 Jan 2022 19:33:39 +0100 Subject: [PATCH 226/226] Update info on copy button --- .../src/components/widgets/types/LogViewerWidget/LogsViewer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md index d41eddbf1..823cd6f2a 100644 --- a/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md +++ b/cogboard-webapp/src/components/widgets/types/LogViewerWidget/LogsViewer.md @@ -90,4 +90,4 @@ Every log column consists of `header` (accordion summary) and `description` (acc Outer container cannot have margins (wrong virtuoso calculations). -Copying feature doesn't work on servers without HTTPS. +Copying feature uses depreciated functions. Recomended ones wouldn't work on unsecured HTTP serwer (w/o HTTPS). There is try catch to remain app stability if those functions were to be removed from browser.