aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/api/api/Controllers/WebSocketController.cs63
-rw-r--r--backend/api/api/Program.cs10
-rw-r--r--backend/api/api/Services/IMLWebSocketService.cs7
-rw-r--r--backend/api/api/Services/MLWebSocketService.cs68
-rw-r--r--backend/api/api/Services/MlConnectionService.cs3
-rw-r--r--backend/microservice/__pycache__/mlservice.cpython-310.pycbin0 -> 5009 bytes
-rw-r--r--backend/microservice/ml_socket.py25
-rw-r--r--frontend/package-lock.json178
-rw-r--r--frontend/package.json1
-rw-r--r--frontend/src/app/_data/Model.ts16
-rw-r--r--frontend/src/app/_elements/dataset-load/dataset-load.component.html51
-rw-r--r--frontend/src/app/_elements/dataset-load/dataset-load.component.ts4
-rw-r--r--frontend/src/app/_elements/datatable/datatable.component.css0
-rw-r--r--frontend/src/app/_elements/datatable/datatable.component.html29
-rw-r--r--frontend/src/app/_elements/datatable/datatable.component.spec.ts25
-rw-r--r--frontend/src/app/_elements/datatable/datatable.component.ts19
-rw-r--r--frontend/src/app/_pages/add-model/add-model.component.html130
-rw-r--r--frontend/src/app/_pages/add-model/add-model.component.ts70
-rw-r--r--frontend/src/app/_services/web-socket.service.spec.ts16
-rw-r--r--frontend/src/app/_services/web-socket.service.ts39
-rw-r--r--frontend/src/app/app.component.html3
-rw-r--r--frontend/src/app/app.module.ts8
-rw-r--r--frontend/src/config.ts3
-rw-r--r--frontend/src/index.html6
24 files changed, 521 insertions, 253 deletions
diff --git a/backend/api/api/Controllers/WebSocketController.cs b/backend/api/api/Controllers/WebSocketController.cs
new file mode 100644
index 00000000..184b47e7
--- /dev/null
+++ b/backend/api/api/Controllers/WebSocketController.cs
@@ -0,0 +1,63 @@
+using api.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Net.WebSockets;
+
+namespace api.Controllers
+{
+ [Route("api")]
+ [ApiController]
+ public class WebSocketController : ControllerBase
+ {
+ private IMLWebSocketService mlWS;
+ public WebSocketController(IMLWebSocketService mlWS)
+ {
+ this.mlWS = mlWS;
+ }
+
+ [HttpGet("wstest")]
+ public string Test()
+ {
+ this.mlWS.Send("ABC123");
+ return "MESSAGE SENT!";
+ }
+
+ [HttpGet("ws")]
+ public async Task Get()
+ {
+ if (HttpContext.WebSockets.IsWebSocketRequest)
+ {
+ using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
+ await Echo(webSocket);
+ }
+ else
+ {
+ HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
+ }
+ }
+
+ private static async Task Echo(WebSocket webSocket)
+ {
+ var buffer = new byte[1024 * 4];
+ var receiveResult = await webSocket.ReceiveAsync(
+ new ArraySegment<byte>(buffer), CancellationToken.None);
+
+ while (!receiveResult.CloseStatus.HasValue)
+ {
+ await webSocket.SendAsync(
+ new ArraySegment<byte>(buffer, 0, receiveResult.Count),
+ receiveResult.MessageType,
+ receiveResult.EndOfMessage,
+ CancellationToken.None);
+
+ receiveResult = await webSocket.ReceiveAsync(
+ new ArraySegment<byte>(buffer), CancellationToken.None);
+ }
+
+ await webSocket.CloseAsync(
+ receiveResult.CloseStatus.Value,
+ receiveResult.CloseStatusDescription,
+ CancellationToken.None);
+ }
+ }
+}
diff --git a/backend/api/api/Program.cs b/backend/api/api/Program.cs
index c8cf0784..5913c2d3 100644
--- a/backend/api/api/Program.cs
+++ b/backend/api/api/Program.cs
@@ -33,6 +33,10 @@ builder.Services.AddScoped<IModelService, ModelService>();
builder.Services.AddScoped<IPredictorService, PredictorService>();
builder.Services.AddScoped<IFileService, FileService>();
+var mlwss = new MLWebSocketService();
+
+builder.Services.AddSingleton<IMLWebSocketService>(mlwss);
+builder.Services.AddHostedService(_ => mlwss);
builder.Services.AddHostedService<TempFileService>();
@@ -54,6 +58,12 @@ builder.Services.AddControllers();
var app = builder.Build();
+var webSocketOptions = new WebSocketOptions
+{
+ KeepAliveInterval = TimeSpan.FromMinutes(2)
+};
+
+app.UseWebSockets(webSocketOptions);
//Add Cors
app.UseCors(
diff --git a/backend/api/api/Services/IMLWebSocketService.cs b/backend/api/api/Services/IMLWebSocketService.cs
new file mode 100644
index 00000000..52efb7fc
--- /dev/null
+++ b/backend/api/api/Services/IMLWebSocketService.cs
@@ -0,0 +1,7 @@
+namespace api.Services
+{
+ public interface IMLWebSocketService
+ {
+ void Send(string message);
+ }
+}
diff --git a/backend/api/api/Services/MLWebSocketService.cs b/backend/api/api/Services/MLWebSocketService.cs
new file mode 100644
index 00000000..ca6919bf
--- /dev/null
+++ b/backend/api/api/Services/MLWebSocketService.cs
@@ -0,0 +1,68 @@
+using System.Net.WebSockets;
+using System.Text;
+
+namespace api.Services
+{
+ public class MLWebSocketService: BackgroundService, IMLWebSocketService
+ {
+ private static readonly string Connection = "ws://localhost:5027";
+
+ private Queue<string> dataQueue = new Queue<string>();
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ using (var socket = new ClientWebSocket())
+ try
+ {
+ await socket.ConnectAsync(new Uri(Connection), stoppingToken);
+
+
+ while(dataQueue.Count > 0)
+ {
+ await Send(socket, dataQueue.Dequeue(), stoppingToken);
+ }
+
+ Receive(socket, stoppingToken);
+
+ await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", stoppingToken);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"ERROR - {ex.Message}");
+ }
+ }
+
+ private async Task Send(ClientWebSocket socket, string data, CancellationToken stoppingToken) =>
+ await socket.SendAsync(Encoding.UTF8.GetBytes(data), WebSocketMessageType.Text, true, stoppingToken);
+
+ private async Task Receive(ClientWebSocket socket, CancellationToken stoppingToken)
+ {
+ var buffer = new ArraySegment<byte>(new byte[2048]);
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ WebSocketReceiveResult result;
+ using (var ms = new MemoryStream())
+ {
+ do
+ {
+ result = await socket.ReceiveAsync(buffer, stoppingToken);
+ ms.Write(buffer.Array, buffer.Offset, result.Count);
+ } while (!result.EndOfMessage);
+
+ if (result.MessageType == WebSocketMessageType.Close)
+ break;
+
+ ms.Seek(0, SeekOrigin.Begin);
+ using (var reader = new StreamReader(ms, Encoding.UTF8))
+ Console.WriteLine(await reader.ReadToEndAsync());
+ }
+ };
+ }
+
+ public void Send(string data)
+ {
+ dataQueue.Enqueue(data);
+ }
+ }
+}
diff --git a/backend/api/api/Services/MlConnectionService.cs b/backend/api/api/Services/MlConnectionService.cs
index 7adade0c..9b167537 100644
--- a/backend/api/api/Services/MlConnectionService.cs
+++ b/backend/api/api/Services/MlConnectionService.cs
@@ -1,4 +1,6 @@
using RestSharp;
+using System.Net.WebSockets;
+using System.Text;
namespace api.Services
{
@@ -11,7 +13,6 @@ namespace api.Services
request.AddJsonBody(model);
var result = await client.ExecuteAsync(request);
return result.Content;//Response od ML microservisa
-
}
}
}
diff --git a/backend/microservice/__pycache__/mlservice.cpython-310.pyc b/backend/microservice/__pycache__/mlservice.cpython-310.pyc
new file mode 100644
index 00000000..c079459a
--- /dev/null
+++ b/backend/microservice/__pycache__/mlservice.cpython-310.pyc
Binary files differ
diff --git a/backend/microservice/ml_socket.py b/backend/microservice/ml_socket.py
new file mode 100644
index 00000000..5489b787
--- /dev/null
+++ b/backend/microservice/ml_socket.py
@@ -0,0 +1,25 @@
+import asyncio
+import websockets
+import json
+
+def get_or_create_eventloop():
+ try:
+ return asyncio.get_event_loop()
+ except RuntimeError as ex:
+ if "There is no current event loop in thread" in str(ex):
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ return asyncio.get_event_loop()
+
+# create handler for each connection
+async def handler(websocket, path):
+ #data = json.loads(await websocket.recv())
+ #reply = f"Data recieved as: {data}!"
+ #print(data['test'])
+ msg = await websocket.recv()
+ await websocket.send("[" + msg + "]")
+
+start_server = websockets.serve(handler, "localhost", 5027)
+
+get_or_create_eventloop().run_until_complete(start_server)
+get_or_create_eventloop().run_forever() \ No newline at end of file
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index b9fd17f8..8c025c8b 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -34,6 +34,7 @@
"ngx-csv-parser": "^0.0.7",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
+ "websocket-ts": "^1.1.1",
"zone.js": "~0.11.4"
},
"devDependencies": {
@@ -446,7 +447,6 @@
"version": "13.2.5",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.5.tgz",
"integrity": "sha512-Xd8xj2Z0ilA4TJAM/JkTtA1CAa6SuebFsEEvabHCRO5MDvtdsIUP91ADUZIqDHy7qe6Qift/rAVN2PXxT2aaNA==",
- "dev": true,
"dependencies": {
"@babel/core": "^7.17.2",
"chokidar": "^3.0.0",
@@ -476,7 +476,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
"integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==",
- "dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.0"
},
@@ -488,7 +487,6 @@
"version": "7.17.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
"integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
- "dev": true,
"dependencies": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
@@ -518,7 +516,6 @@
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -527,7 +524,6 @@
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
"integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
- "dev": true,
"dependencies": {
"@babel/types": "^7.17.0",
"jsesc": "^2.5.1",
@@ -541,7 +537,6 @@
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -781,7 +776,6 @@
"version": "7.16.12",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz",
"integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==",
- "dev": true,
"dependencies": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.16.8",
@@ -811,7 +805,6 @@
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -820,7 +813,6 @@
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -829,7 +821,6 @@
"version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
"integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
- "dev": true,
"dependencies": {
"@babel/types": "^7.16.8",
"jsesc": "^2.5.1",
@@ -843,7 +834,6 @@
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3276,7 +3266,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
- "dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -3550,7 +3539,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -3647,7 +3635,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
@@ -3825,7 +3812,6 @@
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
- "dev": true,
"funding": [
{
"type": "individual",
@@ -4751,7 +4737,6 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
"integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
- "dev": true,
"engines": {
"node": ">= 0.6.0"
}
@@ -5700,7 +5685,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -5869,7 +5853,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -5979,7 +5962,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -6572,7 +6554,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -6626,7 +6607,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6643,7 +6623,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -6670,7 +6649,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -7531,7 +7509,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -7543,7 +7520,6 @@
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
- "dev": true,
"dependencies": {
"sourcemap-codec": "^1.4.4"
}
@@ -8149,7 +8125,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8837,7 +8812,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -9633,7 +9607,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -9644,8 +9617,7 @@
"node_modules/reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
- "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
- "dev": true
+ "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"node_modules/regenerate": {
"version": "1.4.2",
@@ -10066,7 +10038,6 @@
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
- "dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -10451,8 +10422,7 @@
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
- "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
- "dev": true
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"node_modules/spdy": {
"version": "4.0.2",
@@ -10853,7 +10823,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -10919,7 +10888,6 @@
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
- "dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -11415,6 +11383,11 @@
"node": ">=0.8.0"
}
},
+ "node_modules/websocket-ts": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/websocket-ts/-/websocket-ts-1.1.1.tgz",
+ "integrity": "sha512-rm+S60J74Ckw5iizzgID12ju+OfaHAa6dhXhULIOrXkl0e05RzxfY42/vMStpz5jWL3iz9mkyjPcFUY1IgI0fw=="
+ },
"node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -11525,8 +11498,7 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "1.10.2",
@@ -11861,7 +11833,6 @@
"version": "13.2.5",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.5.tgz",
"integrity": "sha512-Xd8xj2Z0ilA4TJAM/JkTtA1CAa6SuebFsEEvabHCRO5MDvtdsIUP91ADUZIqDHy7qe6Qift/rAVN2PXxT2aaNA==",
- "dev": true,
"requires": {
"@babel/core": "^7.17.2",
"chokidar": "^3.0.0",
@@ -11879,7 +11850,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
"integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==",
- "dev": true,
"requires": {
"@jridgewell/trace-mapping": "^0.3.0"
}
@@ -11888,7 +11858,6 @@
"version": "7.17.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
"integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
- "dev": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
@@ -11910,8 +11879,7 @@
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
@@ -11919,7 +11887,6 @@
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
"integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
- "dev": true,
"requires": {
"@babel/types": "^7.17.0",
"jsesc": "^2.5.1",
@@ -11929,8 +11896,7 @@
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
},
@@ -12075,7 +12041,6 @@
"version": "7.16.12",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz",
"integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==",
- "dev": true,
"requires": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.16.8",
@@ -12097,14 +12062,12 @@
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
},
@@ -12112,7 +12075,6 @@
"version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
"integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
- "dev": true,
"requires": {
"@babel/types": "^7.16.8",
"jsesc": "^2.5.1",
@@ -12122,8 +12084,7 @@
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
},
@@ -13281,7 +13242,8 @@
"version": "13.2.5",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.2.5.tgz",
"integrity": "sha512-obiPvwPe+UJUO8cfNbBxukLKG30F+gLF5/erexwklRknJzS4KP8ciH2on6XlTuXUahpDjbO0pffugFE2I/IszQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -13822,7 +13784,8 @@
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"adjust-sourcemap-loader": {
"version": "4.0.0",
@@ -13945,7 +13908,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
- "dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -14143,8 +14105,7 @@
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
},
"bl": {
"version": "4.1.0",
@@ -14215,7 +14176,8 @@
"bootstrap": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
- "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q=="
+ "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
+ "requires": {}
},
"brace-expansion": {
"version": "1.1.11",
@@ -14230,7 +14192,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
"requires": {
"fill-range": "^7.0.1"
}
@@ -14359,7 +14320,6 @@
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
- "dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -14387,7 +14347,8 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz",
"integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"clean-stack": {
"version": "2.2.0",
@@ -14865,7 +14826,8 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz",
"integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"css-select": {
"version": "4.2.1",
@@ -15038,8 +15000,7 @@
"dependency-graph": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
- "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
- "dev": true
+ "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg=="
},
"destroy": {
"version": "1.0.4",
@@ -15687,7 +15648,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
@@ -15810,7 +15770,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
"optional": true
},
"function-bind": {
@@ -15886,7 +15845,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@@ -16143,7 +16101,8 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"ieee754": {
"version": "1.2.1",
@@ -16340,7 +16299,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
@@ -16372,8 +16330,7 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
- "dev": true
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
@@ -16384,7 +16341,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
@@ -16404,8 +16360,7 @@
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-path-cwd": {
"version": "2.2.0",
@@ -16824,7 +16779,8 @@
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz",
"integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"karma-source-map-support": {
"version": "1.4.0",
@@ -17046,7 +17002,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"requires": {
"yallist": "^4.0.0"
}
@@ -17055,7 +17010,6 @@
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
- "dev": true,
"requires": {
"sourcemap-codec": "^1.4.4"
}
@@ -17508,8 +17462,7 @@
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
},
"normalize-range": {
"version": "0.1.2",
@@ -18034,8 +17987,7 @@
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"pify": {
"version": "2.3.0",
@@ -18146,7 +18098,8 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz",
"integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-custom-properties": {
"version": "12.1.4",
@@ -18216,13 +18169,15 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz",
"integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-gap-properties": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz",
"integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-image-set-function": {
"version": "4.0.6",
@@ -18248,7 +18203,8 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz",
"integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-lab-function": {
"version": "4.1.1",
@@ -18275,19 +18231,22 @@
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz",
"integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-media-minmax": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz",
"integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
@@ -18331,13 +18290,15 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz",
"integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-page-break": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz",
"integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-place": {
"version": "7.0.4",
@@ -18402,7 +18363,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz",
"integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"postcss-selector-not": {
"version": "5.0.0",
@@ -18567,7 +18529,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
"requires": {
"picomatch": "^2.2.1"
}
@@ -18575,8 +18536,7 @@
"reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
- "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
- "dev": true
+ "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"regenerate": {
"version": "1.4.2",
@@ -18852,7 +18812,8 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -18881,7 +18842,6 @@
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
- "dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
@@ -19197,8 +19157,7 @@
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
- "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
- "dev": true
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"spdy": {
"version": "4.0.2",
@@ -19406,7 +19365,8 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -19480,7 +19440,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"requires": {
"is-number": "^7.0.0"
}
@@ -19527,8 +19486,7 @@
"typescript": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
- "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
- "dev": true
+ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA=="
},
"ua-parser-js": {
"version": "0.7.31",
@@ -19718,7 +19676,8 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -19874,6 +19833,11 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
+ "websocket-ts": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/websocket-ts/-/websocket-ts-1.1.1.tgz",
+ "integrity": "sha512-rm+S60J74Ckw5iizzgID12ju+OfaHAa6dhXhULIOrXkl0e05RzxfY42/vMStpz5jWL3iz9mkyjPcFUY1IgI0fw=="
+ },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -19940,7 +19904,8 @@
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"y18n": {
"version": "5.0.8",
@@ -19950,8 +19915,7 @@
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "1.10.2",
diff --git a/frontend/package.json b/frontend/package.json
index 678efec1..8cd6db58 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,6 +36,7 @@
"ngx-csv-parser": "^0.0.7",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
+ "websocket-ts": "^1.1.1",
"zone.js": "~0.11.4"
},
"devDependencies": {
diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts
index 0768a374..07364564 100644
--- a/frontend/src/app/_data/Model.ts
+++ b/frontend/src/app/_data/Model.ts
@@ -26,7 +26,9 @@ export default class Model {
public inputLayerActivationFunction: ActivationFunction = ActivationFunction.Sigmoid,
public hiddenLayerActivationFunction: ActivationFunction = ActivationFunction.Sigmoid,
public outputLayerActivationFunction: ActivationFunction = ActivationFunction.Sigmoid,
- public username: string = ''
+ public username: string = '',
+ public nullValues: NullValueOptions = NullValueOptions.DeleteRows,
+ public nullValuesReplacers = []
) { }
}
@@ -96,4 +98,16 @@ export enum Optimizer {
SGD = 'SGD',
SGDMomentum = 'SGDMomentum',
RMSprop = 'RMSprop'
+}
+
+export enum NullValueOptions {
+ DeleteRows = 'delete_rows',
+ DeleteColumns = 'delete_columns',
+ Replace = 'replace'
+}
+
+export enum ReplaceWith {
+ None = '...',
+ Mean = 'Srednja vrednost',
+ Median = 'Medijana'
} \ No newline at end of file
diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.html b/frontend/src/app/_elements/dataset-load/dataset-load.component.html
index 76fc40e2..b159c748 100644
--- a/frontend/src/app/_elements/dataset-load/dataset-load.component.html
+++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.html
@@ -5,57 +5,28 @@
</div>
<div class="col-3">
<label for="name" class="col-form-label">Naziv dataseta:</label>
- <input type="text" class="form-control mb-3" name="name" placeholder="Naziv..."
- [(ngModel)]="dataset.name">
- <label for="desc" class="col-sm-2 col-form-label">Opis:</label>
- <div>
- <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea>
- </div>
+ <input type="text" class="form-control mb-3" name="name" placeholder="Naziv..." [(ngModel)]="dataset.name">
+ <label for="desc" class="col-sm-2 col-form-label">Opis:</label>
+ <div>
+ <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea>
+ </div>
</div>
<div class="col-1">
</div>
<div class="col-4 mt-4">
- <input list=delimiterOptions
- placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2" [(ngModel)]="delimiter"
- (input)="update()">
+ <input list=delimiterOptions placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2"
+ [(ngModel)]="delimiter" (input)="update()">
<datalist id=delimiterOptions>
<option *ngFor="let option of delimiterOptions">{{option}}</option>
</datalist>
<label for="type" class="form-check-label my-5">Da li .csv ima header?
- <input class="mx-3 form-check-input" type="checkbox" (input)="update()" [(ngModel)]="hasHeader" type="checkbox" value="" id="checkboxHeader" checked>
+ <input class="mx-3 form-check-input" type="checkbox" (input)="update()" [(ngModel)]="hasHeader" type="checkbox"
+ value="" id="checkboxHeader" checked>
</label>
<br>
- <input id="fileInput" class="form-control" type="file" class="upload" (change)="changeListener($event)" accept=".csv">
+ <input id="fileInput" class="form-control" type="file" class="upload" (change)="changeListener($event)"
+ accept=".csv">
</div>
</div>
-
- <div class="table-responsive" *ngIf="hasInput">
- <table *ngIf="csvRecords.length > 0 && hasHeader" class="table table-bordered table-light mt-4">
- <thead>
- <tr>
- <th *ngFor="let item of csvRecords[0]; let i = index">{{item}}</th>
- </tr>
- </thead>
- <tbody>
- <tr *ngFor="let row of csvRecords | slice:1:11">
- <td *ngFor="let col of row">{{col}}</td>
- </tr>
- </tbody>
- </table>
-
- <table *ngIf="csvRecords.length > 0 && !hasHeader" class="table table-bordered table-light mt-4">
- <tbody>
- <tr *ngFor="let row of csvRecords | slice:0:10">
- <td *ngFor="let col of row">{{col}}</td>
- </tr>
- </tbody>
- </table>
- </div>
-
- <div *ngIf="csvRecords.length > 0 && hasInput" id="info">
- . . . <br>
- {{rowsNumber}} x {{colsNumber}}
- </div>
-
</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts
index bccf13b7..7f432b9a 100644
--- a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts
+++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts
@@ -15,7 +15,6 @@ export class DatasetLoadComponent {
delimiterOptions: Array<string> = [",", ";", "\t", "razmak", "|"]; //podrazumevano ","
hasHeader: boolean = true;
-
hasInput: boolean = false;
csvRecords: any[] = [];
@@ -39,10 +38,9 @@ export class DatasetLoadComponent {
this.hasInput = false;
return;
}
- else
+ else
this.hasInput = true;
- console.log(this.files);
this.update();
}
diff --git a/frontend/src/app/_elements/datatable/datatable.component.css b/frontend/src/app/_elements/datatable/datatable.component.css
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/frontend/src/app/_elements/datatable/datatable.component.css
diff --git a/frontend/src/app/_elements/datatable/datatable.component.html b/frontend/src/app/_elements/datatable/datatable.component.html
new file mode 100644
index 00000000..2c469ecc
--- /dev/null
+++ b/frontend/src/app/_elements/datatable/datatable.component.html
@@ -0,0 +1,29 @@
+<div *ngIf="data">
+ <div class="table-responsive">
+ <table *ngIf="hasHeader" class="table table-bordered table-light mt-4">
+ <thead>
+ <tr>
+ <th *ngFor="let item of data[0]; let i = index">{{item}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let row of data | slice:1:11">
+ <td *ngFor="let col of row">{{col}}</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table *ngIf="data.length > 0 && !hasHeader" class="table table-bordered table-light mt-4">
+ <tbody>
+ <tr *ngFor="let row of data | slice:0:10">
+ <td *ngFor="let col of row">{{col}}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div id="info">
+ . . . <br>
+ {{data.length}} x {{data[0].length}}
+ </div>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/datatable/datatable.component.spec.ts b/frontend/src/app/_elements/datatable/datatable.component.spec.ts
new file mode 100644
index 00000000..3cf06160
--- /dev/null
+++ b/frontend/src/app/_elements/datatable/datatable.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DatatableComponent } from './datatable.component';
+
+describe('DatatableComponent', () => {
+ let component: DatatableComponent;
+ let fixture: ComponentFixture<DatatableComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ DatatableComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DatatableComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_elements/datatable/datatable.component.ts b/frontend/src/app/_elements/datatable/datatable.component.ts
new file mode 100644
index 00000000..d3740d83
--- /dev/null
+++ b/frontend/src/app/_elements/datatable/datatable.component.ts
@@ -0,0 +1,19 @@
+import { Component, Input, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-datatable',
+ templateUrl: './datatable.component.html',
+ styleUrls: ['./datatable.component.css']
+})
+export class DatatableComponent implements OnInit {
+
+ @Input() hasHeader?: boolean = true;
+
+ @Input() data?: any[] = [];
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/frontend/src/app/_pages/add-model/add-model.component.html b/frontend/src/app/_pages/add-model/add-model.component.html
index 33066f80..afd4ceb4 100644
--- a/frontend/src/app/_pages/add-model/add-model.component.html
+++ b/frontend/src/app/_pages/add-model/add-model.component.html
@@ -3,9 +3,7 @@
</div>
<div id="wrapper">
-
<div id="container" class="container p-5" style="background-color: white; min-height: 100%;">
-
<div class="form-group row mt-3 mb-2 d-flex justify-content-center">
<!--justify-content-center-->
<h2 class="col-2"> Nov model: </h2>
@@ -38,42 +36,39 @@
</button>
<h3 class="mt-3 mx-3">ili</h3>
<button type="button" id="btnNewDataset" class="btn" (click)="viewNewDatasetForm()"
- [ngClass]="{'btnType1': !showMyDatasets, 'btnType2': showMyDatasets}">
+ [ngClass]="{'btnType1': !showMyDatasets, 'btnType2': showMyDatasets}">
Dodajte novi dataset
</button>
</div>
- <!-- POSTOJECI ILI NOVI DATASET -->
-
- <!-- POSTOJECI -->
- <div class="px-5">
- <div *ngIf="showMyDatasets" class="overflow-auto" style="max-height: 500px;">
- <ul class="list-group">
- <li class="list-group-item p-3" *ngFor="let dataset of myDatasets" [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}">
- <app-item-dataset name="usersDataset" [dataset]="dataset" (click)="selectThisDataset(dataset)"></app-item-dataset>
- </li>
- </ul>
- </div>
+ <div class="px-5">
+ <div *ngIf="showMyDatasets" class="overflow-auto" style="max-height: 500px;">
+ <ul class="list-group">
+ <li class="list-group-item p-3" *ngFor="let dataset of myDatasets"
+ [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}">
+ <app-item-dataset name="usersDataset" [dataset]="dataset"
+ (click)="selectThisDataset(dataset)"></app-item-dataset>
+ </li>
+ </ul>
</div>
-
- <!-- NOVI -->
- <app-dataset-load *ngIf="!showMyDatasets" id="dataset" (loaded)="datasetLoaded = true"></app-dataset-load>
- </div>
+ </div>
+ <app-dataset-load *ngIf="!showMyDatasets" id="dataset"
+ (loaded)="datasetLoaded = true; selectedDataset = datasetLoadComponent?.dataset; datasetFile = datasetLoadComponent?.csvRecords; datasetHasHeader = datasetLoadComponent?.hasHeader">
+ </app-dataset-load>
+ <app-datatable [data]="datasetFile" [hasHeader]="datasetHasHeader"></app-datatable>
+ </div>
- <!-- ULAZNE/IZLAZNE KOLONE - POSTOJECI DATASET -->
- <div *ngIf="showMyDatasets && this.selectedDataset" class="mt-4">
- <h2 class="text-center">
- Izabrali ste dataset: <span style="color: #003459; font-weight: bold">{{this.selectedDataset.name}}</span>
- </h2>
- <div class="row mt-5">
+ <!-- ULAZNE/IZLAZNE KOLONE -->
+ <div *ngIf="selectedDataset">
+ <div class="row">
<div class="col d-flex justify-content-center">
<h3>Izaberite ulazne kolone:</h3>
<div id="divInputs" class="form-check mt-2">
<br>
- <div *ngFor="let item of this.selectedDataset.header; let i = index">
- <input class="form-check-input" type="checkbox" value="{{item}}"
- id="cb_{{item}}" name="cbsExisting" checked [disabled]="this.selectedOutputColumnVal == item">&nbsp;
+ <div *ngFor="let item of selectedDataset.header; let i = index">
+ <input class="form-check-input" type="checkbox" value="{{item}}" id="cb_{{item}}"
+ name="cbsNew" checked [disabled]="this.selectedOutputColumnVal == item">&nbsp;
<label class="form-check-label" for="cb_{{item}}">
{{item}}
</label>
@@ -84,45 +79,57 @@
<h3>Izaberite izlaznu kolonu:</h3>
<div id="divOutputs" class="form-check mt-2">
<br>
- <div *ngFor="let item of this.selectedDataset.header; let i = index">
- <input class="form-check-input" type="radio" value="{{item}}"
- id="rb_{{item}}" name="rbsExisting" (change)="this.selectedOutputColumnVal = item">&nbsp;
+ <div *ngFor="let item of selectedDataset.header; let i = index">
+ <input class="form-check-input" type="radio" value="{{item}}" id="rb_{{item}}" name="rbsNew"
+ (change)="this.selectedOutputColumnVal = item">&nbsp;
<label class="form-check-label" for="rb_{{item}}">
{{item}}
</label>
</div>
</div>
</div>
- </div>
- </div>
- <!-- ULAZNE/IZLAZNE KOLONE - NOVI DATASET-->
- <div *ngIf="!showMyDatasets && datasetLoaded">
- <div *ngIf="datasetLoadComponent && datasetLoadComponent.files[0]" class="row">
- <div class="col d-flex justify-content-center">
- <h3>Izaberite ulazne kolone:</h3>
- <div id="divInputs" class="form-check mt-2">
- <br>
- <div *ngFor="let item of datasetLoadComponent.dataset.header; let i = index">
- <input class="form-check-input" type="checkbox" value="{{item}}"
- id="cb_{{item}}" name="cbsNew" checked [disabled]="this.selectedOutputColumnVal == item">&nbsp;
- <label class="form-check-label" for="cb_{{item}}">
- {{item}}
- </label>
- </div>
- </div>
- </div>
- <div class="col d-flex justify-content-left">
- <h3>Izaberite izlaznu kolonu:</h3>
- <div id="divOutputs" class="form-check mt-2">
- <br>
- <div *ngFor="let item of datasetLoadComponent.dataset.header; let i = index">
- <input class="form-check-input" type="radio" value="{{item}}"
- id="rb_{{item}}" name="rbsNew" (change)="this.selectedOutputColumnVal = item">&nbsp;
- <label class="form-check-label" for="rb_{{item}}">
- {{item}}
- </label>
+ <div class="my-2" *ngIf="datasetFile">
+ <h2>Popunjavanje nedostajućih vrednosti:</h2>
+ <div class="form-check">
+ <input type="radio" [(ngModel)]="newModel.nullValues" [value]="NullValueOptions.DeleteRows"
+ class="form-check-input" value="deleteRows" name="fillMissing" id="delRows" checked
+ data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show">
+ <label for="delRows" class="form-check-label">Obriši
+ redove sa nedostajućim vrednostima</label><br>
+ <input type="radio" [(ngModel)]="newModel.nullValues" [value]="NullValueOptions.DeleteColumns"
+ class="form-check-input" value="deleteCols" name="fillMissing" id="delCols"
+ data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show">
+ <label for="delCols" class="form-check-label">Obriši
+ kolone sa nedostajućim vrednostima</label><br>
+ <input type="radio" [(ngModel)]="newModel.nullValues" [value]="NullValueOptions.Replace"
+ class="form-check-input" name="fillMissing" id="replace" data-bs-toggle="collapse"
+ data-bs-target="#fillMissingCustom:not(.show)">
+ <label for="replace" class="form-check-label">Izabraću
+ vrednosti koje će da zamene nedostajuće vrednosti za svaku kolonu...</label><br><br>
+ <div class="collapse" id="fillMissingCustom">
+ <div>
+ <label for="columnReplacers" class="form-label">Unesite zamenu za svaku kolonu:</label>
+ <div id="columnReplacers">
+ <div *ngFor="let column of selectedDataset.header; let i = index" class="my-3">
+ <div class="input-group row" *ngIf="getInputById('cb_'+column).checked">
+ <span class="input-group-text col-4 text-center">
+ {{column}}
+ </span>
+ <input type="text" class="form-control col-4">
+ <select [id]="'replaceOptions'+i" class="form-control col-4"
+ *ngIf="isNumber(datasetFile[1][i])">
+ <option
+ *ngFor="let option of Object.keys(ReplaceWith); let optionName of Object.values(ReplaceWith)"
+ [value]="option">
+ {{ optionName }}
+ </option>
+ </select>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>
@@ -257,7 +264,8 @@
</div>
<div class="col-5">
<label for="splitYesNo" class="form-check-label">Podela test skupa:&nbsp;&nbsp;
- <input id="splitYesNo" class="form-check-input" type="checkbox" [checked]="newModel.randomTestSet"
+ <input id="splitYesNo" class="form-check-input" type="checkbox"
+ [checked]="newModel.randomTestSet"
(change)="newModel.randomTestSet = !newModel.randomTestSet">
</label>
</div>
@@ -288,8 +296,8 @@
<label for="percentage" class="form-label">Procenat podataka koji se uzima za trening skup:</label>
</div>
<div class="col-1">
- <input id="percentage" type="number" class="form-control" min="10" max="90" step="10" value="90" [(ngModel)]="tempTestSetDistribution"
- [disabled] = "!newModel.randomTestSet">
+ <input id="percentage" type="number" class="form-control" min="10" max="90" step="10" value="90"
+ [(ngModel)]="tempTestSetDistribution" [disabled]="!newModel.randomTestSet">
</div>
</div>
diff --git a/frontend/src/app/_pages/add-model/add-model.component.ts b/frontend/src/app/_pages/add-model/add-model.component.ts
index 7bfb7204..1c9198a3 100644
--- a/frontend/src/app/_pages/add-model/add-model.component.ts
+++ b/frontend/src/app/_pages/add-model/add-model.component.ts
@@ -1,10 +1,11 @@
import { Component, OnInit, ViewChild } from '@angular/core';
-import Model from 'src/app/_data/Model';
-import { ANNType, Encoding, ActivationFunction, LossFunction, Optimizer } from 'src/app/_data/Model';
+import Model, { ReplaceWith } from 'src/app/_data/Model';
+import { ANNType, Encoding, ActivationFunction, LossFunction, Optimizer, NullValueOptions } from 'src/app/_data/Model';
import { DatasetLoadComponent } from 'src/app/_elements/dataset-load/dataset-load.component';
import { ModelsService } from 'src/app/_services/models.service';
import shared from 'src/app/Shared';
import Dataset from 'src/app/_data/Dataset';
+import { DatatableComponent } from 'src/app/_elements/datatable/datatable.component';
@Component({
@@ -15,6 +16,7 @@ import Dataset from 'src/app/_data/Dataset';
export class AddModelComponent implements OnInit {
@ViewChild(DatasetLoadComponent) datasetLoadComponent?: DatasetLoadComponent;
+ @ViewChild(DatatableComponent) datatable?: DatatableComponent;
datasetLoaded: boolean = false;
newModel: Model;
@@ -24,7 +26,10 @@ export class AddModelComponent implements OnInit {
ActivationFunction = ActivationFunction;
LossFunction = LossFunction;
Optimizer = Optimizer;
+ NullValueOptions = NullValueOptions;
+ ReplaceWith = ReplaceWith;
Object = Object;
+ document = document;
shared = shared;
selectedOutputColumnVal: string = '';
@@ -33,6 +38,8 @@ export class AddModelComponent implements OnInit {
myDatasets?: Dataset[];
existingDatasetSelected: boolean = false;
selectedDataset?: Dataset;
+ datasetFile?: any[];
+ datasetHasHeader?: boolean = true;
tempTestSetDistribution: number = 90;
@@ -62,9 +69,9 @@ export class AddModelComponent implements OnInit {
addModel() {
if (!this.showMyDatasets)
- this.saveModelWithNewDataset();
+ this.saveModelWithNewDataset();
else
- this.saveModelWithExistingDataset();
+ this.saveModelWithExistingDataset();
}
trainModel() {
@@ -124,14 +131,14 @@ export class AddModelComponent implements OnInit {
if (this.selectedDataset) { //dataset je izabran
this.getCheckedInputCols();
this.getCheckedOutputCol();
-
- if (this.validationInputsOutput()) {
+
+ if (this.validationInputsOutput()) {
this.newModel.datasetId = this.selectedDataset._id;
-
+
this.newModel.randomTestSetDistribution = 1 - Math.round(this.tempTestSetDistribution / 100 * 10) / 10;
this.tempTestSetDistribution = 90;
this.newModel.username = shared.username;
-
+
this.models.addModel(this.newModel).subscribe((response) => {
console.log('ADD MODEL: DONE! REPLY:\n', response);
}, (error) => {
@@ -147,11 +154,9 @@ export class AddModelComponent implements OnInit {
getCheckedInputCols() {
this.newModel.inputColumns = [];
let checkboxes: any;
- if (this.showMyDatasets)
- checkboxes = document.getElementsByName("cbsExisting");
- else
- checkboxes = document.getElementsByName("cbsNew");
-
+
+ checkboxes = document.getElementsByName("cbsNew");
+
for (let i = 0; i < checkboxes.length; i++) {
let thatCb = <HTMLInputElement>checkboxes[i];
if (thatCb.checked == true && thatCb.disabled == false)
@@ -162,10 +167,8 @@ export class AddModelComponent implements OnInit {
getCheckedOutputCol() {
this.newModel.columnToPredict = '';
let radiobuttons: any;
- if (this.showMyDatasets)
- radiobuttons = document.getElementsByName("rbsExisting");
- else
- radiobuttons = document.getElementsByName("rbsNew");
+
+ radiobuttons = document.getElementsByName("rbsNew");
for (let i = 0; i < radiobuttons.length; i++) {
let thatRb = <HTMLInputElement>radiobuttons[i];
@@ -208,6 +211,10 @@ export class AddModelComponent implements OnInit {
if (datasets[i]._id == dataset._id)
}*/
+
+ //this.datasetFile = csvRecords;
+ this.datasetHasHeader = false;
+
this.resetCbsAndRbs();
}
@@ -223,17 +230,8 @@ export class AddModelComponent implements OnInit {
}
checkAllCbs() {
let checkboxes: any;
- //if (this.showMyDatasets)
- checkboxes = document.getElementsByName("cbsExisting");
- //else
- //checkboxes = document.getElementsByName("cbsNew");
-
- for (let i = 0; i < checkboxes.length; i++) {
- (<HTMLInputElement>checkboxes[i]).checked = true;
- (<HTMLInputElement>checkboxes[i]).disabled = false;
- }
- checkboxes = document.getElementsByName("cbsNew");
+ checkboxes = document.getElementsByName("cbsNew");
for (let i = 0; i < checkboxes.length; i++) {
(<HTMLInputElement>checkboxes[i]).checked = true;
(<HTMLInputElement>checkboxes[i]).disabled = false;
@@ -242,16 +240,10 @@ export class AddModelComponent implements OnInit {
uncheckRbs() {
this.selectedOutputColumnVal = '';
let radiobuttons: any;
- //if (this.showMyDatasets)
- radiobuttons = document.getElementsByName("rbsExisting");
- //else
- //radiobuttons = document.getElementsByName("rbsNew");
+ radiobuttons = document.getElementsByName("rbsNew");
for (let i = 0; i < radiobuttons.length; i++)
(<HTMLInputElement>radiobuttons[i]).checked = false;
- radiobuttons = document.getElementsByName("rbsNew");
- for (let i = 0; i < radiobuttons.length; i++)
- (<HTMLInputElement>radiobuttons[i]).checked = false;
}
refreshMyDatasetList() {
@@ -260,4 +252,14 @@ export class AddModelComponent implements OnInit {
});
}
+ isNumber(value: string | number): boolean {
+ return ((value != null) &&
+ (value !== '') &&
+ !isNaN(Number(value.toString())));
+ }
+
+
+ getInputById(id: string): HTMLInputElement {
+ return document.getElementById(id) as HTMLInputElement;
+ }
}
diff --git a/frontend/src/app/_services/web-socket.service.spec.ts b/frontend/src/app/_services/web-socket.service.spec.ts
new file mode 100644
index 00000000..a86aeca7
--- /dev/null
+++ b/frontend/src/app/_services/web-socket.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { WebSocketService } from './web-socket.service';
+
+describe('WebSocketService', () => {
+ let service: WebSocketService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(WebSocketService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_services/web-socket.service.ts b/frontend/src/app/_services/web-socket.service.ts
new file mode 100644
index 00000000..890ada6b
--- /dev/null
+++ b/frontend/src/app/_services/web-socket.service.ts
@@ -0,0 +1,39 @@
+import { Injectable } from '@angular/core';
+import { ConstantBackoff, Websocket, WebsocketBuilder } from 'websocket-ts';
+import { API_SETTINGS } from 'src/config';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class WebSocketService {
+
+ ws?: Websocket;
+
+ private handlers: Function[] = [];
+
+ constructor() {
+ this.ws = new WebsocketBuilder(API_SETTINGS.apiWSUrl)
+ .withBackoff(new ConstantBackoff(30000))
+ .onOpen((i, e) => { console.log('WS: Connected to ' + API_SETTINGS.apiWSUrl) })
+ .onMessage((i, e) => {
+ console.log('WS MESSAGE: ', e.data);
+ this.handlers.forEach(handler => {
+ handler(e.data);
+ })
+ })
+ .onClose((i, e) => { console.log('WS: Connection closed!') })
+ .build();
+ }
+
+ send(msg: string) {
+ this.ws?.send(msg);
+ }
+
+ addHandler(handler: Function) {
+ this.handlers.push(handler);
+ }
+
+ removeHandler(handler: Function) {
+ this.handlers.splice(this.handlers.indexOf(handler), 1);
+ }
+}
diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index 2ec16fc2..f44a6d00 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -3,4 +3,5 @@
<router-outlet></router-outlet>
<!--<app-barchart></app-barchart>
<app-scatterchart></app-scatterchart>-->
-</div> \ No newline at end of file
+</div>
+<app-notifications></app-notifications> \ No newline at end of file
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 9d2bbc26..4612e3a7 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -6,8 +6,8 @@ import { HttpClientModule } from '@angular/common/http';
import { MatSliderModule } from '@angular/material/slider';
import { MatIconModule } from '@angular/material/icon';
-import {NgChartsModule} from 'ng2-charts';
-import { Ng2SearchPipe, Ng2SearchPipeModule } from 'ng2-search-filter';
+import { NgChartsModule } from 'ng2-charts';
+import { Ng2SearchPipeModule } from 'ng2-search-filter';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { DatasetLoadComponent } from './_elements/dataset-load/dataset-load.component';
@@ -33,6 +33,8 @@ import { BrowsePredictorsComponent } from './_pages/browse-predictors/browse-pre
import { PredictComponent } from './_pages/predict/predict.component';
import { ScatterchartComponent } from './scatterchart/scatterchart.component';
import { BarchartComponent } from './barchart/barchart.component';
+import { NotificationsComponent } from './_elements/notifications/notifications.component';
+import { DatatableComponent } from './_elements/datatable/datatable.component';
import { FilterDatasetsComponent } from './_pages/filter-datasets/filter-datasets.component';
@NgModule({
@@ -57,6 +59,8 @@ import { FilterDatasetsComponent } from './_pages/filter-datasets/filter-dataset
PredictComponent,
ScatterchartComponent,
BarchartComponent,
+ NotificationsComponent,
+ DatatableComponent,
FilterDatasetsComponent
],
imports: [
diff --git a/frontend/src/config.ts b/frontend/src/config.ts
index 8c48672e..f1c14194 100644
--- a/frontend/src/config.ts
+++ b/frontend/src/config.ts
@@ -1,3 +1,4 @@
export const API_SETTINGS = {
- apiURL: 'http://localhost:5283/api'
+ apiURL: 'http://localhost:5283/api',
+ apiWSUrl: 'ws://localhost:5283/api/websocket/ws'
} \ No newline at end of file
diff --git a/frontend/src/index.html b/frontend/src/index.html
index 0079969e..1461c9ae 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -1,5 +1,6 @@
<!doctype html>
<html lang="en">
+
<head>
<meta charset="utf-8">
<title>Frontend</title>
@@ -10,8 +11,9 @@
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
+
<body class="mat-typography">
<app-root></app-root>
- <script src="node_modules/chart.js/src/chart.js"></script>
</body>
-</html>
+
+</html> \ No newline at end of file