GithubHelp home page GithubHelp logo

hivemq / hivemq-edge Goto Github PK

View Code? Open in Web Editor NEW
80.0 14.0 16.0 17.44 MB

HiveMQ Edge is an MQTT gateway that enables interoperability between OT devices and IT systems. It translates diverse protocols into MQTT for streamlined communication and helps organize data into a unified namespace, making managing and streaming data across your infrastructure easier.

Home Page: http://hivemq.com

License: Apache License 2.0

Shell 0.14% Batchfile 0.05% HTML 0.36% JavaScript 0.11% TypeScript 11.02% Java 88.29% CSS 0.01% SCSS 0.01% Kotlin 0.03%
edge java mqtt mqtt-sn open-source ads bacnet http modbus opc-ua

hivemq-edge's People

Contributors

a-imal avatar antpaw avatar bogdanstirbat avatar brian-gilmore-hivemq avatar dc2-danielkrueger avatar gitseti avatar h2xd avatar hlohse avatar linomp avatar owen-compton avatar remit avatar renovate[bot] avatar schaebo avatar sfrehse avatar simon622 avatar tgracchus avatar vanch3d avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hivemq-edge's Issues

Consider the sidebar icons

The sidebar nav icons at the moment have not been discussed. We should ensure they have meaning and are distinct. Presently we have duplicates which seem to just have the meaning of "links".

I can image we want iconography to support the various entity/functional areas of the system.

Adapter documentation links yield 404

Expected behavior

The protocol adapters have links to documentation rendered in the web application. These should link to the wiki docs. In fact they link to the main website which results in a 404

  • Affected HiveMQ Edge version(s): 2023.2

Auto-detections of Adapter's topics in the frontend

For usage in the workspace an other components, we need to be able to extract the topics defined in every adapter, regardless of their internal structure

While a generic mapping could be delivered as part of the consolidation of the backend's adapter definition (see ?????), this is a job that in fact can be easily implemented in the frontend

The current definition of all known adapters is using a custom format attribute :

/// opc-ua
'mqtt-topic': {
  type: 'string',
  title: 'Destination MQTT topic',
  description: 'The MQTT topic to publish to',
  format: 'mqtt-topic',
},
/// modbus
destination: {
  type: 'string',
  title: 'Destination Topic',
  description: 'The topic to publish data on',
  format: 'mqtt-topic',
},

OPC-UA adapter unable to use host.docker.internal URI

Expected behavior

Within the HiveMQ Edge Docker container, hostname host.docker.internal can be used to connect to an OPC-UA server running on the Docker host machine

Actual behavior

OPC-UA protocol adapter errors with log message:

ERROR - Not able to connect and subscribe to OPC-UA server opc.tcp://host.docker.internal:49320

java.util.concurrent.CompletionException: java.net.UnknownHostException: {Docker Host Hostname}: No address associated with hostname

Caused by: java.net.UnknownHostException: {Docker Host Hostname}: No address associated with hostname

To Reproduce

Steps

  1. Configure OPC-UA server on Docker host machine (probably optional)
  2. Start HiveMQ Edge Docker container
  3. Configure new OPC-UA Adapter connection to host machine, using host.docker.internal as hostname

Reproducer code

Details

  • Affected HiveMQ Edge version(s): hivemq/hivemq-edge:2023.4
  • Used JVM version: openjdk 11.0.19 2023-04-18

Show human readable "Last started" column in list of adapters

Expected behavior

Show the last started column in a more human-readable for, e.g., "15 days ago", "1h ago"

Actual behavior

The column only shows the unit "minutes", e.g., "1234 mins ago" which isn't very readable.
Screenshot 2023-08-31 at 19 09 02

To Reproduce

Create a protocol adapter and wait for an hour. The respective component sits in the frontend.

Steps

Reproducer code

Details

  • Affected HiveMQ Edge version(s): Since 2023.1.
  • Used JVM version: Every

The change is suggested to be implemented it in the frontend since the backend is correctly returning the timestamp.

Improvements for `com.hivemq.util.Strings.convertBytes`

Expected behavior

Method com.hivemq.util.Strings.convertBytes has unit tests.

Actual behavior

Method com.hivemq.util.Strings.convertBytes has no unit tests.

To Reproduce

No steps to reproduce required.

Steps

No steps to reproduce required.

Reproducer code

No steps to reproduce required.

Details

Add several improvements to com.hivemq.util.Strings.convertBytes:

  • rename to toHumanReadableFormat, I think that it's a more readable name

  • add unit tests

  • Question. Method FileUtils.byteCountToDisplaySize from the commons-io library (already used in the project) does almost the same thing, more details here, the exception is that FileUtils.byteCountToDisplaySize is less precise (sizes without additional decimal points, e.g. 4 GB). Should FileUtils.byteCountToDisplaySize be used instead?

  • Affected HiveMQ Edge version(s):

  • Used JVM version:

Wrong installation description for windows

In the wiki chapter 'Installing HiveMQ Edge/Windows Systems/Manual installation', the first task ist to run ./gradlew clean hivemqZip but this is no valid gradle task. The actual distribution task should be ./gradlew clean hivemqEdgeZip.

Start & Stop Bridges

We should allow bridges to be independently started and stopped via API and UI. A new action on the overflow menu to START | STOP | RESTART the connection.

Add unit tests for com.hivemq.util.Files

Expected behavior

The class com.hivemq.util.Files has unit tests.

Actual behavior

The class com.hivemq.util.Files has no unit tests.

To Reproduce

No reproduction steps required.

Steps

No reproduction steps required.

Reproducer code

No reproduction steps required.

Details

Unit tests should be added to com.hivemq.util.Files.

Also, during the addition of these tests, a change behavior was detected (and fixed) for the method call getFileExtension("/home/pa.th/somefile")

  • Affected HiveMQ Edge version(s): latest version (master)
  • Used JVM version: Java 11

Remove wrapped fields duplicate title and description

Expected behavior

The custom schema generator should not generate duplicate title and description fields for wrapped types.

Actual behavior

Title and description fields are duplicated on the field and item level leading to form display issues.

Old JWT Tokens being presented to the API

Over a number of weeks I have seen behaviour (seemingly after a CRUD event) where the browser suddenly logs out. There seems to be no obvious reason for this, as the JWT is only a few minutes old.

Today I captured the event in logs where a previously issued JWE was presented to the API and rejected (causing the logout event).

Please see the log attached where a JWT is issues at the start of the session, then on a subsequent operation a previous token is presented.

2023-08-18 16:54:37,739 [pool-29-thread-1] JwtAuthenticationProvider ERROR- jwt validation failed, reason JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature: JsonWebSignature{"kid":"00001","alg":"RS256"}->eyJraWQiOiIwMDAwMSIsImFsZyI6IlJTMjU2In0.eyJqdGkiOiJZSkgxaGJ4aFJvZ1VDOHpMZjJfeWdnIiwiaWF0IjoxNjkyMzczODk2LCJhdWQiOiJIaXZlTVEtRWRnZS1BcGkiLCJpc3MiOiJIaXZlTVEtRWRnZSIsImV4cCI6MTY5MjM3NTY5NiwibmJmIjoxNjkyMzczNzc2LCJzdWIiOiJhZG1pbiIsInJvbGVzIjpbImFkbWluIl19.CPwswyJtkeHvOYWTK9Y6DTcEAA-2QeMKfWB-c80bAZbKmLacDjlcekHJM7L66HI1qG4MH3urWljQa4G0zbNztGoMu9NVp6EIAc_UW4YNSx-ovaFTAZLPyfFYvWtSlDjN84A5CZ2FVeKBTiBaO7QsvQ47_ak5dl4CtLZ_yD2HG01GzvkN9Lhk6046P8cT_SO_Bmsij4F7R1RsZAxa1mBTHAS8eKvexwAMzwWsXsvxha5imHsd14aIX8Poe321R_gZkQnooTRduisYsVxyMVaJZu3GSAfYF2xzxZ9UkwzUM4TTn2JiexVPIDm02m4xqgGu3rxe_4Sosz82Hy--MzY_lA]


2023-08-18 16:54:41,707 [pool-29-thread-2] JwtAuthenticationProvider INFO- Generated JWE 'eyJraWQiOiIwMDAwMSIsImFsZyI6IlJTMjU2In0.eyJqdGkiOiIwZmdDX3gzcHJrdHVwbWRLTHhMVUpnIiwiaWF0IjoxNjkyMzc0MDgxLCJhdWQiOiJIaXZlTVEtRWRnZS1BcGkiLCJpc3MiOiJIaXZlTVEtRWRnZSIsImV4cCI6MTY5MjM3NTg4MSwibmJmIjoxNjkyMzczOTYxLCJzdWIiOiJhZG1pbiIsInJvbGVzIjpbImFkbWluIl19.AtdhBfZiLJxiFmZMtFjmQmUhwMaG31ZklIIOdNrrh94C3w4Pr7v-Rn-k0D7VdlkF-LyamZUUAGIr4JG8Xse9NovKX8vBwvSodTOKv-9JBF5PB4Q3Tj_1GHGSTKXwzz6X2W339y18r0kwQp_hBt_Tl9mSHA4reIoAUJuB4SXfYZvHCoIcbnMqVgdZKt2i_xeCWsjvuB8vlsJ7Dm8EIdDrgJLHIVQhIb4Sv4cx0Lk_umiPAa3Kj7Ufyfg2n7G8zbF_VNnoPQdqmdJd1hpn3AYbnx5HwaplFtv5qafCHC572214UwOKqPx8mSrkvSeBtg08qPmolryavsgGjh5E48G2_w' for principal ApiPrincipal{name='admin', roles=[admin]}

2023-08-18 16:54:50,796 [pool-29-thread-1] JwtAuthenticationProvider ERROR- jwt validation failed, reason JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature: JsonWebSignature{"kid":"00001","alg":"RS256"}->eyJraWQiOiIwMDAwMSIsImFsZyI6IlJTMjU2In0.eyJqdGkiOiJZSkgxaGJ4aFJvZ1VDOHpMZjJfeWdnIiwiaWF0IjoxNjkyMzczODk2LCJhdWQiOiJIaXZlTVEtRWRnZS1BcGkiLCJpc3MiOiJIaXZlTVEtRWRnZSIsImV4cCI6MTY5MjM3NTY5NiwibmJmIjoxNjkyMzczNzc2LCJzdWIiOiJhZG1pbiIsInJvbGVzIjpbImFkbWluIl19.CPwswyJtkeHvOYWTK9Y6DTcEAA-2QeMKfWB-c80bAZbKmLacDjlcekHJM7L66HI1qG4MH3urWljQa4G0zbNztGoMu9NVp6EIAc_UW4YNSx-ovaFTAZLPyfFYvWtSlDjN84A5CZ2FVeKBTiBaO7QsvQ47_ak5dl4CtLZ_yD2HG01GzvkN9Lhk6046P8cT_SO_Bmsij4F7R1RsZAxa1mBTHAS8eKvexwAMzwWsXsvxha5imHsd14aIX8Poe321R_gZkQnooTRduisYsVxyMVaJZu3GSAfYF2xzxZ9UkwzUM4TTn2JiexVPIDm02m4xqgGu3rxe_4Sosz82Hy--MzY_lA]

Kubernetes

Problem or use case

Can you put the helm charts or deployment yaml files for running hivemq-edge on Kubernetes? Kubernetes for edge computing is becoming the standard.

Preferred solution or suggestions

Run ./gradlew clean hivemqEdgeZip task already exists

Expected behavior

The gradle build should complete succesful and a zip should be the output

Actual behavior

Build fails directly with message: Cannot add task 'hivemqEdgeZip' as a task with that name already exists.

Stacktrace:
Build file FOLDERNAME line: 91

Cannot add task 'hivemqEdgeZip' as a task with that name already exists.

  • Try:

Run with --info or --debug option to get more log output.
Run with --scan to get full insights.

  • Exception is:
    org.gradle.api.internal.tasks.DefaultTaskContainer$DuplicateTaskException: Cannot add task 'hivemqEdgeZip' as a task with that name already exists.
    at org.gradle.api.internal.tasks.DefaultTaskContainer.failOnDuplicateTask(DefaultTaskContainer.java:257)
    at org.gradle.api.internal.tasks.DefaultTaskContainer.registerTask(DefaultTaskContainer.java:398)
    at org.gradle.api.internal.tasks.DefaultTaskContainer.register(DefaultTaskContainer.java:375)
    at org.gradle.kotlin.dsl.TaskContainerExtensionsKt.provideDelegate(TaskContainerExtensions.kt:140)
    at Build_gradle.(build.gradle.kts:91)
    at Program.execute(Unknown Source)
    at org.gradle.kotlin.dsl.execution.Interpreter$ProgramHost.eval(Interpreter.kt:532)
    at org.gradle.kotlin.dsl.execution.Interpreter$ProgramHost.evaluateSecondStageOf(Interpreter.kt:438)
    at Program.execute(Unknown Source)
    at org.gradle.kotlin.dsl.execution.Interpreter$ProgramHost.eval(Interpreter.kt:532)
    at org.gradle.kotlin.dsl.execution.Interpreter.eval(Interpreter.kt:184)
    at org.gradle.kotlin.dsl.provider.StandardKotlinScriptEvaluator.evaluate(KotlinScriptEvaluator.kt:115)
    at org.gradle.kotlin.dsl.provider.KotlinScriptPluginFactory$create$1.invoke(KotlinScriptPluginFactory.kt:51)
    at org.gradle.kotlin.dsl.provider.KotlinScriptPluginFactory$create$1.invoke(KotlinScriptPluginFactory.kt:36)
    at org.gradle.kotlin.dsl.provider.KotlinScriptPlugin.apply(KotlinScriptPlugin.kt:34)
    at org.gradle.configuration.BuildOperationScriptPlugin$1.run(BuildOperationScriptPlugin.java:65)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
    at org.gradle.configuration.BuildOperationScriptPlugin.lambda$apply$0(BuildOperationScriptPlugin.java:62)
    at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)
    at org.gradle.configuration.BuildOperationScriptPlugin.apply(BuildOperationScriptPlugin.java:62)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:360)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:378)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:359)
    at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:42)
    at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:26)
    at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:35)
    at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:109)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:360)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$2(DefaultProjectStateRegistry.java:408)
    at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:270)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:408)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:389)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:359)
    at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:100)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
    at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:72)
    at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:760)
    at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:151)
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.ensureConfigured(DefaultProjectStateRegistry.java:328)
    at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:33)
    at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:47)
    at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:50)
    at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:64)
    at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
    at org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40)
    at org.gradle.initialization.VintageBuildModelController.lambda$prepareProjects$3(VintageBuildModelController.java:89)
    at org.gradle.internal.model.StateTransitionController.lambda$doTransition$12(StateTransitionController.java:227)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:238)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:226)
    at org.gradle.internal.model.StateTransitionController.lambda$transitionIfNotPreviously$10(StateTransitionController.java:201)
    at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:34)
    at org.gradle.internal.model.StateTransitionController.transitionIfNotPreviously(StateTransitionController.java:197)
    at org.gradle.initialization.VintageBuildModelController.prepareProjects(VintageBuildModelController.java:89)
    at org.gradle.initialization.VintageBuildModelController.prepareToScheduleTasks(VintageBuildModelController.java:71)
    at org.gradle.internal.build.DefaultBuildLifecycleController.lambda$prepareToScheduleTasks$2(DefaultBuildLifecycleController.java:134)
    at org.gradle.internal.model.StateTransitionController.lambda$doTransition$12(StateTransitionController.java:227)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:238)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:226)
    at org.gradle.internal.model.StateTransitionController.lambda$maybeTransition$9(StateTransitionController.java:187)
    at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:34)
    at org.gradle.internal.model.StateTransitionController.maybeTransition(StateTransitionController.java:183)
    at org.gradle.internal.build.DefaultBuildLifecycleController.prepareToScheduleTasks(DefaultBuildLifecycleController.java:132)
    at org.gradle.internal.buildtree.DefaultBuildTreeWorkPreparer.scheduleRequestedTasks(DefaultBuildTreeWorkPreparer.java:33)
    at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$doScheduleAndRunTasks$2(DefaultBuildTreeLifecycleController.java:89)
    at org.gradle.composite.internal.DefaultIncludedBuildTaskGraph.withNewWorkGraph(DefaultIncludedBuildTaskGraph.java:75)
    at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.doScheduleAndRunTasks(DefaultBuildTreeLifecycleController.java:88)
    at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$runBuild$4(DefaultBuildTreeLifecycleController.java:106)
    at org.gradle.internal.model.StateTransitionController.lambda$transition$6(StateTransitionController.java:166)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:238)
    at org.gradle.internal.model.StateTransitionController.lambda$transition$7(StateTransitionController.java:166)
    at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:44)
    at org.gradle.internal.model.StateTransitionController.transition(StateTransitionController.java:166)
    at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.runBuild(DefaultBuildTreeLifecycleController.java:103)
    at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.scheduleAndRunTasks(DefaultBuildTreeLifecycleController.java:69)
    at org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:53)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.internal.buildtree.ProblemReportingBuildActionRunner.run(ProblemReportingBuildActionRunner.java:49)
    at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:69)
    at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:119)
    at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
    at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.lambda$execute$0(RootBuildLifecycleBuildActionExecutor.java:40)
    at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:128)
    at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.execute(RootBuildLifecycleBuildActionExecutor.java:40)
    at org.gradle.internal.buildtree.DefaultBuildTreeContext.execute(DefaultBuildTreeContext.java:40)
    at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.lambda$execute$0(BuildTreeLifecycleBuildActionExecutor.java:65)
    at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53)
    at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.execute(BuildTreeLifecycleBuildActionExecutor.java:65)
    at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:61)
    at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:57)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor.execute(RunAsBuildOperationBuildActionExecutor.java:57)
    at org.gradle.launcher.exec.RunAsWorkerThreadBuildActionExecutor.lambda$execute$0(RunAsWorkerThreadBuildActionExecutor.java:36)
    at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:270)
    at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:119)
    at org.gradle.launcher.exec.RunAsWorkerThreadBuildActionExecutor.execute(RunAsWorkerThreadBuildActionExecutor.java:36)
    at org.gradle.tooling.internal.provider.ContinuousBuildActionExecutor.execute(ContinuousBuildActionExecutor.java:103)
    at org.gradle.tooling.internal.provider.SubscribableBuildActionExecutor.execute(SubscribableBuildActionExecutor.java:64)
    at org.gradle.internal.session.DefaultBuildSessionContext.execute(DefaultBuildSessionContext.java:46)
    at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter$ActionImpl.apply(BuildSessionLifecycleBuildActionExecuter.java:100)
    at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter$ActionImpl.apply(BuildSessionLifecycleBuildActionExecuter.java:88)
    at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:69)
    at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:62)
    at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:41)
    at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:63)
    at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
    at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58)
    at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42)
    at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
    at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
    at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
    at org.gradle.util.internal.Swapper.swap(Swapper.java:38)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
    at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)

To Reproduce

Checkout the project switch to tagge commit 2023.4 or stay on master. Run ./gradlew clean hivemqEdgeZip in console or execute task in Intellij gradle

  • Affected HiveMQ Edge version(s): 2023.4

Adapter metadata should describe whether it supports discovery

Current discovery is an advanced adapter feature, which only some adapters support. In anticipation of UI and API calling these, we need to provide an indication as to wwhether the underlying type supports it. e.g. HTTP will never support discovery.
New method added to the type metadata, new abstraction of discoverValues in place for default implementations and strong gatekeeping around the API method.

Test Connection

User Story

As a HiveMQ Edge user I want to test my connection parameters before committing the configuration, to enable me to validate my parameters or understand what parameters might be incorrectly set

Value

  • Prevents frustrations in returning to configs
  • Prevents loss of time in production scenarios
  • Enables users to validate parameters and understand where issues may be occurring

Definition of Done

  • The connection config menu for each protocol adapter has a 'test connection' button
  • Feedback is clear in the UI either the connection test is good or it fails
  • Failure data is presented to the user for them to understand the issue (this can be a later iteration)

Add custom topic editing to the bridge

User Story

As a HiveMQ Edge user, I want to recognise that I'm editing a topic for the bridge and how that topic might relate to previous configuration

Value

  • Enable users with a consistent approach to visualising and editing topic, segments and topic tree

Definition of Done
This ticket is part of an initiative consolidating the visualisation, selection and editing of topics across the Edge. See (#138, #139 and #140)

  • The current input component is refactored to be used across Bridge and Adapters
  • The new input component is an open text with auto-completion
  • Options of the auto-completion select component are provided by all the topics defined through the app's topic tree
  • Rendering of the options should reflect the design choices of the topic tree selectors
  • Rendering of the selected topics should reflect the design choices of both topics and topic tree

Image

Edge Connect - Ability to discover Edge instances on the same network

Problem or use case

When more than one Edge instance is running, we should be able to visually see and inspect the other nodes, and connect them via a transparent MQTT bridge.

Phase 1
Add configuration which represents a node in the graph from XML
Add a UDP broadcast and response engine that allows dynamic population of the above
Add a real-time model which maintains a network graph of instances
Expose this model through the API
Add a new side bar item "Edge Connect" which when clicks shows edge instances in the network (inluding self).

Phase 2
Add the ability to CONNECT x2 instances of Edge together via a bidirectional wildcard subscription so messages are synchronised across the Edge instances

Phase 3
Add the ability to synchronise/replicate adapter configuration across the instances

Create default logo template and sizing for adapters

Currently all logos are sourced and created in different styles. We should standardise the naming and the design of these. We should work with burkhard & team to come up with a common template which we can add to along with defining common dimensions.

Start & Stop Adapters

Now we have introduced isolated states for runtime and connection status, it gives us far more control to be able to start / stop individual instances. Depending on the implementation of the adapter, some adapters will NOT try to reestablish a connection on an invalid set of details after a number of backoff retries (they will be transitioned from connection ERROR back to DISCONNECTED - but still running) where they will stay until they are edited. We should allow more control on these, but allowing a START | STOP | RESTART on each instance in the control actions.

We may also wish to consider the fundamental control/state cycle of each adapter. Suggest we detail some rheudamentary state transition diagrams for this work unit.

Contextual error message on fields doesn't pass accessibility standards

Expected behaviour

The font colour for the component should pass the WCAG 2.1 AA colour-contrast threshold test

Actual behaviour

The current (and default) implementation of the error message below a field uses a red colour that is at times too light for the white background. The contrast is not sufficient from an accessibility point-of-view and breaks the "best practice" guidelines of the WCAG tests

Image

To Reproduce

Check any field with a contextual error message, using either the Axe tool or the Cypress tests.

Details

  • Note that the contrast is also impacted by the choice of font size. Choosing a small font will increase the risk of falling the threshold

Image

  • change the theme for the FormErrorMessage to adjust font colour

UNS Button text only makes sense for first use

The UNS CTA is a button that reads "+ Setup a unified namespace". This works great for first use or when there is no UNS defined. We should detect is there is a UNS setup already and change the text of the button accordingly... when one is setup the button text should read:

"Modify unified namespace"

Screenshot 2023-09-20 at 18 26 11

Include Modbus Function Read Coil

Problem or use case

Some Modbus Server only allow reading coils.

Preferred solution or suggestions

In addition to reading holding registers, other modbus functions like reading coils discrete inputs and input registers should be possible.

Adapter identifier regex needs to have start and end tokens

Expected behavior

It should not be possible to add a whitespace prefix or suffix to the id field. If you manipulate the form this is possible.

Actual behavior

To Reproduce

Steps

Reproducer code

Details

  • Affected HiveMQ Edge version(s):
  • Used JVM version:

Break the adapter hierarchy to abstract from polling or self-managed

Problem or use case

Presently the adapter abstraction is too coarse and all extended types are bound to the polling type which isn't required.

Preferred solution or suggestions

The adapters should be updated to specify the runtime management out of the box using Start and stopInternal methods which manage the runtime state - on the base type.

We should then introduce a polling version which adds polling functionality and binds to polling configuration as standard by its generic typing.

Create default run configurations for edge project

Presently there are no run configurations created for the open-source project. We should create x2 run configurations to make it easy for developers to run in the development environment easily.

  • Java configuration for development mode, which bootstraps the modules from your local workspace not export location
  • Java configuration for creating a new module (CreateAdapterBlueprint#main)

Add a `uiSchema` property to the payload of a protocol adapter type

The react-jsonschema-form library is used to create a form out of the JSON Schema specification of an adapter. It introduced a secondary schema to provide information on how the form should be rendered, uiSchema .

See https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema for a description of the schema.

At the moment, the uiSchema is hardcoded in the frontend and contains the combined information of most of the known adapters, by property name. This is not a sustainable situation, for two reasons:

  • The number of adapters is increasing fast and are not all incorporated
  • Different adapters might use the same property name for different attributes

The uiSchema should therefore be located within each adapters, along their configSchema.

The schema is a JSON object, whose attributes are described in the document above. If strongly typed, the description of the UiSchema type can be found here: https://github.com/rjsf-team/react-jsonschema-form/blob/6ffc2ceb1f24e2661d0acbc0c3e09e5e443e9f27/packages/utils/src/types.ts#L784

To the list of default options, we have added a new one to combine top-level properties into separate tabs:

{
  "ui:tabs": [
    {
      "id": "coreFields",
      "title": "title",
      "properties": ["id", "port", "host", "uri", "url", "pollingIntervalMillis"]
    },
    {
      "id": "subFields",
      "title": "Subscription",
      "properties": ["subscriptions"]
    }
  ]
}

S7 Protocol Adapter

Now we have the abstractions in place, we need to exposer and test the S7 protocol adapter.

Http adapter showing "connected" status even if nothing running on designated server

Expected behavior

An active HTTP adapter should not indicate connected when it is blatant that the server is not running.

Actual behavior

Image

Image

Both the frontend and the backend accept a clearly fake URL. Upon creation, the active adapter is updated and the status switches to connected

To Reproduce

Creating an HTTP adapter with the URL: http://fake

Details

Other adapters like MODBUS or OPC-UA are shown as disconnected when the "URL" of the service is not a live system.

Note: there is also a question as to http://fake is a valid URL

Improvements for com.hivemq.util.ObjectMemoryEstimation

Expected behavior

Class com.hivemq.util.ObjectMemoryEstimation has unit tests.

Actual behavior

Class com.hivemq.util.ObjectMemoryEstimation doesn't has unit tests.

To Reproduce

No steps to reproduce required.

Steps

No steps to reproduce required.

Reproducer code

No steps to reproduce required.

Details

Add several improvements to the class com.hivemq.util.ObjectMemoryEstimation:

  • rename to MemoryEstimator (in my opinion it's a more readable name)

  • remove method calls that retrieve constant values, here is explained why it's better to use the constant value instead of having a function that returns a constant value

  • add unit tests

  • add Java doc

  • Affected HiveMQ Edge version(s): latest version (master)

  • Used JVM version: Java 11

Design the topic tree selector in the workspace

User Story

As a HiveMQ Edge user, I want to recognise on the workspace the topics that I've created through the different elements (bridges and adapters).

As a HiveMQ Edge user, I want to be able to filter parts of the workspace out, using the interactive selector as a visualisation of the topic tree

Value

  • Enable users with a consistent approach to visualising and editing topics, segments and topic tree

Definition of Done
This ticket is part of an initiative consolidating the visualisation, selection and editing of topics across the Edge. See (#138, #139 and #140)

  • The topic tree of the Edge is defined as the collection of all the topics "configured" in bridges and adapters
  • The topic tree is visualised using a customised version of a sunburst diagram, an extension of a doughnut chart suitable for hierarchical data (see https://datavizproject.com/data-type/sunburst-diagram/)
  • The topic tree is interactive, allowing users to select individual topics (i.e. full paths of the diagram) or segments (i.e. partial concentric circles) to filter the workspace out
  • The relevant topics are visualised on each node where they are defined, matching both the style of the editor and the style of the topic tree
  • The relevant nodes are filtered out (or in), to highlight the subgraph matching the conditions of the selected topic tree

UNS form submission with single characters leads to API side error

Expected behaviour

When the UNS form is filled with a single character in each of the fields, I expect this to be a valid submission.

Actual behavior

The UI encounters (what appears to be) an API side error and displays the error in a red toast.

To Reproduce

Add single character values to your UNS

  • Affected HiveMQ Edge version(s): 2023.5

Screenshot 2023-09-07 at 10 03 10

Add a custom widget for the topics in the Adapter forms

The editing of topics in the app is not consistent:

  • the Bridge editor uses an auto-complete select (without any options added)
  • the Adapter editor uses a plain text input without any formatting
  • the Workspace shows the topic with a specific (if minimalist) rendering

Image

Image

Image

Image

The discrepancy is partly due to the difficulty in customising the JSONSchema-driven form generation. With the recent mastery of the format option (see #110), it is now possible to customise the form at that level

We should convey a sense of consistency to the users, strongly emphasising that they are creating MQTT topics:

  • Both Bridge and Adapter editors should use the same UI widget
  • The Topic input should use the auto-complete select widget, with the list of existing workspace-wide topics as options
  • The individual topics should be render consistently across both editors and workspace
  • The individual topics should be relatable to the workspace-wide topic tree under construction

Pattern-based validations need a better UX approach

Expected behaviour

The error message for the validation of a url field should tell humans what the error is about, not regex parser :-)

Actual behaviour

The standard error message for a pattern attribute of a JSONSchema string is literally transcribing the wronged regular expression. Which, in the case of a url`, can be long and unreadable, therefore unusable

Image

To Reproduce

Creating an HTTP adapter with an incorrect URL, like in #106

Details

  • consider the implication for both (or either) backend and frontend validation
  • human-readable (short?) statement would be better (suggestion, drop the printout of the regex)
  • in the frontend, consider using destructured validation to pinpoint errors and utilise field-associated error messages:
    • the domain xxx is not valid
    • the protocol httpxyz is not valid

User Interface not rendering when device is offline

Expected behavior

The user interface experience should render and be fully available when the device has no access to the internet.

Actual behavior

The user interface hangs on the login page

To Reproduce

Run HiveMQ Edge on a device with no internet connectivity

  • Affected HiveMQ Edge version(s): 2023.3

"Modbus" vs "ModBus"

The Modbus Organization specifically defines "Modbus" as the correct capitalization

Several instances of "ModBus" observed in UI video

NA

Normalize all spellings to Modbus

Reproducer code

Details

  • Affected HiveMQ Edge version(s):
  • Used JVM version:

Add custom topic editing to the adapters

User Story

As a HiveMQ Edge user, I want to recognise that I'm editing a topic for an adapter and how that topic might relate to previous configuration

Value

  • Enable users with a consistent approach to visualising and editing topics, segments and topic tree

Definition of Done
This ticket is part of an initiative consolidating the visualisation, selection and editing of topics across the Edge. See (#138, #139 and #140 )

  • The current input component is refactored to be used across Bridge and Adapters
  • The adapter form is generated using a custom UISchema associated with the format mqtt-topic, as extracted from their JSONSchema configuration
  • The new input component is an open text with auto-completion
  • Options of the auto-completion select component are provided by all the topics defined through the app's topic tree
  • Rendering of the options should reflect the design choices of the topic tree selectors
  • Rendering of the selected topics should reflect the design choices of both topics and topic tree

Image

To do

  • JSONSchemaForm: create a custom BaseInputTemplate to deal with mqtt-format
  • CreatableSelect: create a custom auto-completion selector for the topics
    • support single and multiple items
    • custom rendering for options
  • ReactQuery: refactor the extraction of topics from the data sources (bridges and adapters)
    • support publish-only filtering
  • JSONSchemaForm: create a custom validation rule to validate topics (e.g. rejecting wildcards, ...)

XML Namespaces - The xmlns Attribute is missing from the example configuration XML files

Expected layout

<hivemq xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="config.xsd">

        <mqtt-listeners>
            <tcp-listener>.....

Actual file layout

<hivemq>
    <mqtt-listeners>
        <tcp-listener>
            <port>1883</port>

ahh, makes sense: config.xsd is not there either. But is referenced by default config.xml so....maybe adding this and referencing in the examples xml makes more sense here.

Documentation for opc ua adapter is missing

Problem or use case

Currently the section for the OPCUA adapter in the documentation is empty.

Preferred solution or suggestions

Add documentation into the section or, if not available, add text indicating this.

(Re)-Add the re-issuance filter to the UI application

Problem or use case

When the API detects a request time that sits within an expiry window that is 10% of the token length, it will include a header on all secured API requests with a new JWE.

Preferred solution or suggestions

The UI should detect the existence to the HTTP header X-Bearer-Token-Reissue and update its local cache with this new token.

Java only?

Problem or use case

Will the HiveMQ be available only in Java? Any plans for C/C++, Rust?

Preferred solution or suggestions

Improve UX flow when network connection errors

Expected behaviour

When connection to the backend cannot be secured because of network errors, the user should be notified appropriately and certain operations (like creating a new adapter) should be restricted

Actual behaviour

At the moment, most of the error handling has been focusing on receiving errors (4xx or 5xx) from the backend. But the cases where the requests cannot even be sent due to network issues have not been covered.

The frontend, being a Single-Page Application and using React Query caching facilities, continues to render relatively appropriately. Some of the icons are not properly loaded but the caches still provide all the information to populate the React pages.

It includes accessing and interacting with forms, even if it means sending data through requests that are not operational.

There is not a single feedback about the connection status to the user until a cache becomes "stale" is fetched again and the failure of X retries triggers the error

To Reproduce

Access the frontend, navigate through the different pages (to make sure all the requests are accessed once and cached) then shut the backend down.

Alternatively, use the browser throttling capability to simulate "offline" behaviour.

Details

Originally posted by @vanch3d in #95 (comment)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.