19 Commits

Author SHA1 Message Date
dymik739 e65bfa501a fix image for kubernetes 2025-05-12 20:47:11 +03:00
dymik739 1d5157b635 fix all issues 2025-05-12 20:47:11 +03:00
dymik739 99e91324bc add Dockerfile for building the container 2025-05-12 20:47:11 +03:00
Kirill 6d2533e53d Merge branch 'feature/com-control-base-path' into 'main'
fixed path for swagger

See merge request kzotkin/hiveemulator!8
2025-04-23 08:58:51 +00:00
Kirill Zotkin fb36a7d13b fixed path for swagger 2025-04-23 11:57:36 +03:00
Kirill f1d8b95744 Merge branch 'feature/com-control-base-path' into 'main'
added base path to communication control

See merge request kzotkin/hiveemulator!7
2025-04-21 19:41:23 +00:00
Kirill Zotkin 3fa3ef13b2 added base path to communication control 2025-04-21 22:38:07 +03:00
Kirill Zotkin dd189fc094 fixed Readme for HiveMind 2025-04-06 22:08:37 +03:00
Kirill Zotkin e341fc14b3 reverted Redis connection string 2025-04-06 20:53:32 +03:00
Kirill Zotkin 2c50dc75eb enable swagger for prod 2025-04-06 15:39:39 +03:00
Kirill Zotkin 4df4437487 Merge branch 'main' of https://gitlab.com/kzotkin/hiveemulator 2025-03-18 22:40:19 +02:00
Kirill 17ceed960e Merge branch 'DroneConnectionFix' into 'main'
Drone connection fix

See merge request kzotkin/hiveemulator!5
2025-03-17 08:39:56 +00:00
dsokolovrudakov b0bdf686d2 Add HiveReconnectedMessage 2025-03-12 21:45:40 +02:00
dsokolovrudakov 4390a523c7 Fix Hive Mind connect logic - allow to reconnect gracefully 2025-03-12 21:42:55 +02:00
Kirill Zotkin bee604d68e always start swagger 2025-03-12 00:24:56 +02:00
Kirill 26c4c8e00c Merge branch 'WebUI' into 'main'
Change WebUI to read API URL not from env but from config file

See merge request kzotkin/hiveemulator!4
2025-03-11 09:39:25 +00:00
dsokolovrudakov a0e70bd3c4 Change WebUI to read API URL not from env but from config file 2025-03-09 22:19:42 +02:00
Kirill 08f30fc7fc Merge branch 'Cleanup' into 'main'
Program.cs cleanup, HiveModel change, log level change

See merge request kzotkin/hiveemulator!3
2025-02-20 14:41:51 +00:00
dsokolovrudakov 9329d76ab6 Fix returned status codes depending on scenario, provide informative logs
Extend logic of encpoints to provide more informative status codes on different scenarios (not found, bad request, etc). Update logging to write structured and informative logs
2025-02-20 00:05:33 +02:00
22 changed files with 311 additions and 110 deletions
+5 -1
View File
@@ -1,7 +1,11 @@
src/MapClient/node_modules src/MapClient/node_modules
src/MapClient/dist
src/CommunicationControl/.idea src/CommunicationControl/.idea
src/CommunicationControl/.vs src/CommunicationControl/.vs
bin bin
obj obj
Logs Logs
.vscode .vscode
build
src/CommunicationControl/DevOpsProject.sln.DotSettings.user
src/CommunicationControl/DevOpsProject.sln.DotSettings.user
+70
View File
@@ -0,0 +1,70 @@
# dotnet build environment
FROM alpine:latest as dotnet
WORKDIR /
RUN apk add dotnet8-sdk git
RUN git clone https://gitlab.com/kzotkin/hiveemulator
WORKDIR /hiveemulator/src/CommunicationControl/
RUN dotnet build DevOpsProject/DevOpsProject.CommunicationControl.API.csproj
RUN dotnet build DevOpsProject.HiveMind.API/DevOpsProject.HiveMind.API.csproj
# nodejs build environment
#FROM alpine:latest as nodejs
#WORKDIR /
#RUN apk add git npm
#RUN git clone https://gitlab.com/kzotkin/hiveemulator
#WORKDIR /hiveemulator/src/MapClient/
#RUN npm install
#RUN npm run build
# production environment
FROM alpine:latest
WORKDIR /
#RUN apk add dotnet8-sdk redis openrc lighttpd npm
#RUN apk add git dotnet8-sdk redis openrc npm
#RUN apk add git aspnetcore8-runtime redis openrc npm
RUN apk add aspnetcore8-runtime redis openrc npm
#RUN git clone https://gitlab.com/kzotkin/hiveemulator
#COPY --from=nodejs /hiveemulator/ /hiveemulator/
COPY --from=dotnet /hiveemulator/ /hiveemulator/
#COPY --from=nodejs /hiveemulator/src/MapClient/dist/* /var/www/localhost/htdocs/
#RUN mkdir /hive-cc
#COPY --from=0 /hiveemulator/src/CommunicationControl/DevOpsProject/* /hive-cc/
#COPY --from=dotnet /hiveemulator/src/CommunicationControl/DevOpsProject/* /hiveemulator/src/CommunicationControl/DevOpsProject/
#COPY --from=dotnet /hiveemulator/src/CommunicationControl/DevOpsProject.HiveMind.API/* /hiveemulator/src/CommunicationControl/DevOpsProject.HiveMind.API/
#RUN mkdir /hive-hm
#COPY --from=0 /hiveemulator/src/CommunicationControl/DevOpsProject.HiveMind.API/* /hive-hm/
WORKDIR /hiveemulator/src/MapClient/
RUN npm install
RUN sed -i 's/localhost/10\.1\.1\.2/' public/config.json
WORKDIR /
COPY ./daemon-files/hive-cc /etc/init.d/hive-cc
COPY ./daemon-files/hive-hm /etc/init.d/hive-hm
COPY ./daemon-files/hive-map /etc/init.d/hive-map
RUN chmod u+x /etc/init.d/hive-hm /etc/init.d/hive-cc /etc/init.d/hive-map
#RUN mkdir /etc/rulevels/{stage-redis,stage-cc,stage-hm,stage-map}
#RUN rc-update add redis default
#RUN rc-update add hive-hm default
#RUN rc-update add hive-cc default
#RUN rc-update add hive-map default
CMD sh -c "openrc default ; \
rc-service redis start ; \
rc-service hive-cc start ; \
rc-service hive-hm start ; \
rc-service hive-map start ; \
sleep 96h; \
exec sh"
+24 -2
View File
@@ -8,6 +8,11 @@
- [Communiction Control](#communiction-control) - [Communiction Control](#communiction-control)
- [Hive Mind](#hive-mind) - [Hive Mind](#hive-mind)
- [Usage](#usage) - [Usage](#usage)
- [Build](#build)
- [Map Clinet](#map-clinet)
- [Communiction Control](#communiction-control-1)
- [Hive Mind](#hive-mind-1)
- [Communiction Control](#communiction-control-2)
## About ## About
This is a demo project used in the Uni DevOps course This is a demo project used in the Uni DevOps course
@@ -39,7 +44,7 @@ dotnet run --project DevOpsProject/DevOpsProject.CommunicationControl.API.cspro
```bash ```bash
cd src/CommunicationControl cd src/CommunicationControl
dotnet run --project DevOpsProject/DevOpsProject.HiveMind.API.csproj dotnet run --project DevOpsProject.HiveMind.API/DevOpsProject.HiveMind.API.csproj
``` ```
@@ -53,4 +58,21 @@ dotnet run --project DevOpsProject/DevOpsProject.HiveMind.API.csproj
get [hiveKey] get [hiveKey]
``` ```
3. Communication Control Swagger: http://localhost:8080 3. Communication Control Swagger: http://localhost:8080
## Build
### Map Clinet
cd src/MapClient
npm install
npm run build
### Communiction Control
cd src/CommunicationControl
dotnet publish -p:PublishProfile=FolderProfile --artifacts-path=build/CommunicationControl DevOpsProject/DevOpsProject.CommunicationControl.API.csproj
### Hive Mind
### Communiction Control
cd src/CommunicationControl
dotnet publish -p:PublishProfile=FolderProfile --artifacts-path=build/HiveMind DevOpsProject/DevOpsProject.HiveMind.API.csproj
+14
View File
@@ -0,0 +1,14 @@
#!/sbin/openrc-run
command="./DevOpsProject.CommunicationControl.API"
command_args=""
pidfile="/run/hive-cc.pid"
supervisor="supervise-daemon"
start_pre() {
cd /hiveemulator/src/CommunicationControl/DevOpsProject/bin/Debug/net8.0/
}
depend() {
need redis
}
+14
View File
@@ -0,0 +1,14 @@
#!/sbin/openrc-run
command="./DevOpsProject.HiveMind.API"
command_args=""
pidfile="/run/hive-hm.pid"
supervisor="supervise-daemon"
start_pre() {
cd /hiveemulator/src/CommunicationControl/DevOpsProject.HiveMind.API/bin/Debug/net8.0/
}
depend() {
need hive-cc
}
+14
View File
@@ -0,0 +1,14 @@
#!/sbin/openrc-run
command="npm"
command_args="run dev"
pidfile="/run/hive-map.pid"
supervisor="supervise-daemon"
depend() {
need hive-cc
}
start_pre() {
cd /hiveemulator/src/MapClient/
}
@@ -1,4 +1,4 @@
using DevOpsProject.CommunicationControl.Logic.Services.Interfaces; using DevOpsProject.CommunicationControl.Logic.Services.Interfaces;
using DevOpsProject.Shared.Clients; using DevOpsProject.Shared.Clients;
using DevOpsProject.Shared.Configuration; using DevOpsProject.Shared.Configuration;
using DevOpsProject.Shared.Enums; using DevOpsProject.Shared.Enums;
@@ -37,7 +37,7 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
bool isSuccessfullyDisconnected = false; bool isSuccessfullyDisconnected = false;
try try
{ {
var result = await _redisService.DeleteAsync(hiveId); var result = await _redisService.DeleteAsync(GetHiveKey(hiveId));
isSuccessfullyDisconnected = result; isSuccessfullyDisconnected = result;
return result; return result;
} }
@@ -65,61 +65,79 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
public async Task<HiveOperationalArea> ConnectHive(HiveModel model) public async Task<HiveOperationalArea> ConnectHive(HiveModel model)
{ {
bool isHiveAlreadyConnected = await IsHiveConnected(model.HiveID);
if (isHiveAlreadyConnected)
{
_logger.LogWarning("Reconnect Hive request: {@model}", model);
}
else
{
_logger.LogInformation("Trying to connect Hive: {@model}", model);
}
bool result = await _redisService.SetAsync(GetHiveKey(model.HiveID), model); bool result = await _redisService.SetAsync(GetHiveKey(model.HiveID), model);
if (result) if (result)
{ {
_logger.LogInformation("Successfully connected Hive: {@model}", model);
var operationalArea = _spatialService.GetHiveOperationalArea(model); var operationalArea = _spatialService.GetHiveOperationalArea(model);
await _messageBus.Publish(new HiveConnectedMessage if (isHiveAlreadyConnected)
{ {
HiveID = model.HiveID, await _messageBus.Publish(new HiveReconnectedMessage
Hive = model, {
InitialOperationalArea = operationalArea, HiveID = model.HiveID,
IsSuccessfullyConnected = result Hive = model,
}); InitialOperationalArea = operationalArea,
IsSuccessfullyReconnected = result
});
}
else
{
await _messageBus.Publish(new HiveConnectedMessage
{
HiveID = model.HiveID,
Hive = model,
InitialOperationalArea = operationalArea,
IsSuccessfullyConnected = result
});
}
return operationalArea; return operationalArea;
} }
else else
{ {
await _messageBus.Publish(new HiveConnectedMessage _logger.LogError("Failed to connect Hive: {@model}", model);
{
HiveID = model.HiveID,
Hive = model,
IsSuccessfullyConnected = result
});
throw new HiveConnectionException($"Failed to connect hive for HiveId: {model.HiveID}"); throw new HiveConnectionException($"Failed to connect hive for HiveId: {model.HiveID}");
} }
} }
public async Task<bool> IsHiveConnected(string hiveId)
{
string hiveKey = GetHiveKey(hiveId);
return await _redisService.CheckIfKeyExists(hiveKey);
}
public async Task<DateTime> AddTelemetry(HiveTelemetryModel model) public async Task<DateTime> AddTelemetry(HiveTelemetryModel model)
{ {
string hiveKey = GetHiveKey(model.HiveID); string hiveKey = GetHiveKey(model.HiveID);
bool hiveExists = await _redisService.CheckIfKeyExists(hiveKey); bool result = await _redisService.UpdateAsync(hiveKey, (HiveModel hive) =>
if (hiveExists)
{ {
bool result = await _redisService.UpdateAsync(hiveKey, (HiveModel hive) => hive.Telemetry = model;
{ });
hive.Telemetry = model;
});
await _messageBus.Publish(new TelemetrySentMessage if (result)
{ {
HiveID = model.HiveID, _logger.LogInformation("Telemetry updated for HiveID: {hiveId}. Updated telemetry timestamp: {timestamp}", model.HiveID, model.Timestamp);
Telemetry = model,
IsSuccessfullySent = result
});
return model.Timestamp;
} }
else else
{ {
await _messageBus.Publish(new TelemetrySentMessage _logger.LogError("Failed to update Telemetry - Redis update issue. HiveID: {hiveId}, Telemetry model: {@telemetry}", model.HiveID, model);
{
HiveID = model.HiveID,
Telemetry = model,
IsSuccessfullySent = false
});
throw new HiveNotFoundException($"Hive not found for id: {model.HiveID}");
} }
await _messageBus.Publish(new TelemetrySentMessage
{
HiveID = model.HiveID,
Telemetry = model,
IsSuccessfullySent = result
});
return model.Timestamp;
} }
public async Task<string> SendHiveControlSignal(string hiveId, Location destination) public async Task<string> SendHiveControlSignal(string hiveId, Location destination)
@@ -127,33 +145,40 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
var hive = await GetHiveModel(hiveId); var hive = await GetHiveModel(hiveId);
if (hive == null) if (hive == null)
{ {
throw new Exception($"Hive control signal error: cannot find hive with id: {hiveId}"); _logger.LogError("Sending Hive Control signal: Hive not found for HiveID: {hiveId}", hiveId);
return null;
} }
bool isSuccessfullySent = false; bool isSuccessfullySent = false;
string hiveMindPath = _communicationControlConfiguration.CurrentValue.HiveMindPath;
var command = new MoveHiveMindCommand
{
CommandType = State.Move,
Location = destination,
Timestamp = DateTime.Now
};
try try
{ {
var command = new MoveHiveMindCommand var result = await _hiveHttpClient.SendHiveControlCommandAsync(hive.HiveSchema, hive.HiveIP, hive.HivePort, hiveMindPath, command);
{
CommandType = State.Move,
Location = destination,
Timestamp = DateTime.Now
};
var result = await _hiveHttpClient.SendHiveControlCommandAsync(hive.HiveSchema, hive.HiveIP, hive.HivePort,
_communicationControlConfiguration.CurrentValue.HiveMindPath, command);
isSuccessfullySent = true; isSuccessfullySent = true;
return result; return result;
} }
finally finally
{ {
await _messageBus.Publish(new MoveHiveMessage if (isSuccessfullySent)
{ {
IsSuccessfullySent = isSuccessfullySent, await _messageBus.Publish(new MoveHiveMessage
Destination = destination, {
HiveID = hiveId IsSuccessfullySent = isSuccessfullySent,
}); Destination = destination,
HiveID = hiveId
});
}
else
{
_logger.LogError("Failed to send control command for Hive: {@hive}, path: {path}, \n Command: {@command}", hive, hiveMindPath, command);
}
} }
} }
@@ -8,6 +8,7 @@ namespace DevOpsProject.CommunicationControl.Logic.Services.Interfaces
Task<HiveModel> GetHiveModel(string hiveId); Task<HiveModel> GetHiveModel(string hiveId);
Task<List<HiveModel>> GetAllHives(); Task<List<HiveModel>> GetAllHives();
Task<HiveOperationalArea> ConnectHive(HiveModel model); Task<HiveOperationalArea> ConnectHive(HiveModel model);
Task<bool> IsHiveConnected(string hiveId);
Task<DateTime> AddTelemetry(HiveTelemetryModel model); Task<DateTime> AddTelemetry(HiveTelemetryModel model);
Task<string> SendHiveControlSignal(string hiveId, Location destination); Task<string> SendHiveControlSignal(string hiveId, Location destination);
} }
@@ -57,11 +57,8 @@ using (var scope = app.Services.CreateScope())
app.UseExceptionHandler(); app.UseExceptionHandler();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) app.UseSwagger();
{ app.UseSwaggerUI();
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors(corsPolicyName); app.UseCors(corsPolicyName);
@@ -27,6 +27,7 @@ namespace DevOpsProject.HiveMind.Logic.Services
// If already moving - stop movement // If already moving - stop movement
if (HiveInMemoryState.IsMoving) if (HiveInMemoryState.IsMoving)
{ {
_logger.LogWarning("Previous movement command terminated. Previous destination: {@destination}, Current Location: {@current}, new destination: {@destination}", HiveInMemoryState.Destination, HiveInMemoryState.CurrentLocation, destination);
StopMovement(); StopMovement();
} }
@@ -39,8 +40,9 @@ namespace DevOpsProject.HiveMind.Logic.Services
if (_movementTimer == null) if (_movementTimer == null)
{ {
// TODO: Recalculating position each N seconds // TODO: Recalculating position each N seconds
_movementTimer = new Timer(UpdateMovement, null, TimeSpan.Zero, TimeSpan.FromSeconds(3)); int intervalFromSeconds = 3;
_logger.LogInformation("Movement timer started."); _movementTimer = new Timer(UpdateMovement, null, TimeSpan.Zero, TimeSpan.FromSeconds(intervalFromSeconds));
_logger.LogInformation("Movement timer started. Destination: {@destination}, recalculation interval: {interval}", destination, intervalFromSeconds);
} }
} }
} }
@@ -60,14 +62,13 @@ namespace DevOpsProject.HiveMind.Logic.Services
if (AreLocationsEqual(currentLocation.Value, destination.Value)) if (AreLocationsEqual(currentLocation.Value, destination.Value))
{ {
_logger.LogInformation("Reached destination. Current location: {@currentLocation}, Destination: {@destination}", currentLocation, destination);
StopMovement(); StopMovement();
return; return;
} }
Location newLocation = CalculateNextPosition(currentLocation.Value, destination.Value, 0.1f); Location newLocation = CalculateNextPosition(currentLocation.Value, destination.Value, 0.1f);
HiveInMemoryState.CurrentLocation = newLocation; HiveInMemoryState.CurrentLocation = newLocation;
_logger.LogInformation($"Moved closer: {newLocation}");
} }
} }
@@ -77,7 +78,6 @@ namespace DevOpsProject.HiveMind.Logic.Services
_movementTimer = null; _movementTimer = null;
HiveInMemoryState.IsMoving = false; HiveInMemoryState.IsMoving = false;
HiveInMemoryState.Destination = null; HiveInMemoryState.Destination = null;
_logger.LogInformation("Movement stopped: Reached destination.");
} }
private static bool AreLocationsEqual(Location loc1, Location loc2) private static bool AreLocationsEqual(Location loc1, Location loc2)
@@ -48,6 +48,8 @@ namespace DevOpsProject.HiveMind.Logic.Services
}; };
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
_logger.LogInformation("Attempting to connect Hive. Request: {@request}, URI: {uri}", request, uriBuilder.Uri);
var retryPolicy = Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode) var retryPolicy = Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync( .WaitAndRetryAsync(
10, 10,
@@ -1,19 +0,0 @@
namespace DevOpsProject.Shared.Exceptions
{
public class HiveNotFoundException : Exception
{
public HiveNotFoundException()
{
}
public HiveNotFoundException(string message)
: base(message)
{
}
public HiveNotFoundException(string message, Exception inner)
: base(message, inner)
{
}
}
}
@@ -0,0 +1,16 @@
using DevOpsProject.Shared.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DevOpsProject.Shared.Messages
{
public class HiveReconnectedMessage : BaseMessage
{
public bool IsSuccessfullyReconnected { get; set; }
public HiveModel Hive { get; set; }
public HiveOperationalArea InitialOperationalArea { get; set; }
}
}
@@ -1,2 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACorsPolicyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ff2b268c5e2cd9f1f915a357be6a8df853a5e36d3641a02dea9c31d924ca17a1_003FCorsPolicyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConnectionMultiplexer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F777330fcb5805c27956d104ff76bca3d9647f0def3f139feed35c0ef42c745_003FConnectionMultiplexer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACorsPolicyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ff2b268c5e2cd9f1f915a357be6a8df853a5e36d3641a02dea9c31d924ca17a1_003FCorsPolicyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
@@ -32,17 +32,21 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
[HttpGet("hive/{hiveId}")] [HttpGet("hive/{hiveId}")]
public async Task<IActionResult> GetHive(string hiveId) public async Task<IActionResult> GetHive(string hiveId)
{ {
var hiveModel = await _communicationControlService.GetHiveModel(hiveId); var hiveExists = await _communicationControlService.IsHiveConnected(hiveId);
if (!hiveExists)
{
_logger.LogWarning("Failed to get Hive for HiveID: {hiveId}", hiveId);
return NotFound($"Hive with HiveID: {hiveId} is not found");
}
var hiveModel = await _communicationControlService.GetHiveModel(hiveId);
return Ok(hiveModel); return Ok(hiveModel);
} }
[HttpGet("hive")] [HttpGet("hive")]
public async Task<IActionResult> GetHives() public async Task<IActionResult> GetHives()
{ {
var hives = await _communicationControlService.GetAllHives(); var hives = await _communicationControlService.GetAllHives();
return Ok(hives); return Ok(hives);
} }
@@ -59,6 +63,7 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
if (request?.Hives == null || !request.Hives.Any()) if (request?.Hives == null || !request.Hives.Any())
return BadRequest("No hive IDs provided."); return BadRequest("No hive IDs provided.");
_logger.LogInformation("Hive moving request accepted by enpdoint. Request: {@request}", request);
foreach (var id in request.Hives) foreach (var id in request.Hives)
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
@@ -69,7 +74,7 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, $"Failed to send control signal for HiveID: {id}"); _logger.LogError(ex, "Failed to send control signal for HiveID: {id} \n Request: {@request}", id, request);
} }
}); });
} }
@@ -11,10 +11,12 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
public class HiveController : Controller public class HiveController : Controller
{ {
private readonly ICommunicationControlService _communicationControlService; private readonly ICommunicationControlService _communicationControlService;
private readonly ILogger<HiveController> _logger;
public HiveController(ICommunicationControlService communicationControlService) public HiveController(ICommunicationControlService communicationControlService, ILogger<HiveController> logger)
{ {
_communicationControlService = communicationControlService; _communicationControlService = communicationControlService;
_logger = logger;
} }
[HttpPost("connect")] [HttpPost("connect")]
@@ -36,6 +38,7 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
}; };
return Ok(connectResponse); return Ok(connectResponse);
} }
[HttpPost("telemetry")] [HttpPost("telemetry")]
@@ -51,13 +54,22 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
Timestamp = DateTime.Now Timestamp = DateTime.Now
}; };
var telemetryUpdateTimestamp = await _communicationControlService.AddTelemetry(hiveTelemetryModel); bool isHiveConnected = await _communicationControlService.IsHiveConnected(request.HiveID);
var telemetryResponse = new HiveTelemetryResponse if (isHiveConnected)
{ {
Timestamp = telemetryUpdateTimestamp var telemetryUpdateTimestamp = await _communicationControlService.AddTelemetry(hiveTelemetryModel);
}; var telemetryResponse = new HiveTelemetryResponse
{
Timestamp = telemetryUpdateTimestamp
};
return Ok(telemetryResponse); return Ok(telemetryResponse);
}
else
{
_logger.LogWarning("Failed to write telemetry. Hive with HiveID: {hiveId} is not connected. Request: {@request}", request.HiveID, request);
return NotFound($"Failed to write telemetry. Hive with HiveID: {request.HiveID} is not connected");
}
} }
} }
@@ -12,7 +12,5 @@ namespace DevOpsProject.CommunicationControl.API.DI
return serviceCollection; return serviceCollection;
} }
} }
} }
@@ -19,6 +19,8 @@ internal class Program
// TODO: consider this approach // TODO: consider this approach
builder.Services.AddJsonControllerOptionsConfiguration(); builder.Services.AddJsonControllerOptionsConfiguration();
string basePath = builder.Configuration.GetValue<string>("BasePath") ?? string.Empty;
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c => builder.Services.AddSwaggerGen(c =>
{ {
@@ -41,15 +43,26 @@ internal class Program
builder.Services.AddProblemDetails(); builder.Services.AddProblemDetails();
var app = builder.Build(); var app = builder.Build();
if (!string.IsNullOrEmpty(basePath))
{
var pathBase = new PathString(basePath);
app.UsePathBase(pathBase);
app.Use(async (context, next) =>
{
context.Request.PathBase = pathBase;
await next();
});
}
app.UseExceptionHandler(); app.UseExceptionHandler();
if (app.Environment.IsDevelopment()) app.UseSwagger();
app.UseSwaggerUI(c =>
{ {
app.UseSwagger(); c.SwaggerEndpoint($"{basePath}/swagger/v1/swagger.json", "CommunicationControl - V1");
app.UseSwaggerUI(); c.RoutePrefix = $"swagger";
}
});
app.UseCors(corsPolicyName); app.UseCors(corsPolicyName);
app.UseAuthorization(); app.UseAuthorization();
@@ -20,6 +20,7 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"Urls": "http://0.0.0.0:8080", "Urls": "http://0.0.0.0:8080",
"BasePath": "",
"Serilog": { "Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": { "MinimumLevel": {
+3
View File
@@ -0,0 +1,3 @@
{
"API": "http://localhost:8080/api/v1/client"
}
+6 -6
View File
@@ -3,9 +3,9 @@ import axios from "axios";
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
// Fetch the center coordinates for the initial map load // Fetch the center coordinates for the initial map load
export const fetchCenterCoordinates = async () => { export const fetchCenterCoordinates = async (apiUrl) => {
try { try {
const response = await axios.get(`${API_BASE_URL}/area`); const response = await axios.get(`${apiUrl}/area`);
return response.data; return response.data;
} catch (error) { } catch (error) {
console.error("Error fetching center coordinates:", error); console.error("Error fetching center coordinates:", error);
@@ -14,9 +14,9 @@ export const fetchCenterCoordinates = async () => {
}; };
// Fetch all hives and extract their latitude/longitude // Fetch all hives and extract their latitude/longitude
export const fetchHives = async () => { export const fetchHives = async (apiUrl) => {
try { try {
const response = await axios.get(`${API_BASE_URL}/hive`); const response = await axios.get(`${apiUrl}/hive`);
return response.data.map(hive => ({ return response.data.map(hive => ({
id: hive.HiveID, id: hive.HiveID,
@@ -31,9 +31,9 @@ export const fetchHives = async () => {
}; };
// Move all hives to a new location // Move all hives to a new location
export const moveHives = async (lat, lon, ids) => { export const moveHives = async (apiUrl, lat, lon, ids) => {
try { try {
await axios.patch(`${API_BASE_URL}/hive`, { await axios.patch(`${apiUrl}/hive`, {
Hives: ids, Hives: ids,
Destination: { Destination: {
Latitude: lat, Latitude: lat,
+10 -3
View File
@@ -21,6 +21,7 @@ const MapView = () => {
const mapRef = useRef(null); const mapRef = useRef(null);
const vectorLayerRef = useRef(null); const vectorLayerRef = useRef(null);
const initialized = useRef(false); const initialized = useRef(false);
const apiUrl = useRef(null)
const [hives, setHives] = useState([]); const [hives, setHives] = useState([]);
const [popup, setPopup] = useState({ visible: false, coords: null }); const [popup, setPopup] = useState({ visible: false, coords: null });
const [mouseCoords, setMouseCoords] = useState({ lat: "", lon: "" }); const [mouseCoords, setMouseCoords] = useState({ lat: "", lon: "" });
@@ -28,11 +29,17 @@ const MapView = () => {
useEffect(() => { useEffect(() => {
const initializeMap = async () => { const initializeMap = async () => {
const res = await fetch('/config.json')
const data = await res.json()
apiUrl.current = data.API
if (initialized.current) return; if (initialized.current) return;
initialized.current = true; initialized.current = true;
try { try {
const center = await fetchCenterCoordinates(); const center = await fetchCenterCoordinates(apiUrl.current);
if (center) { if (center) {
initMap(center.Latitude, center.Longitude); initMap(center.Latitude, center.Longitude);
await fetchAndDrawHives(); await fetchAndDrawHives();
@@ -66,7 +73,7 @@ const MapView = () => {
// Fetch hives and draw them on the map // Fetch hives and draw them on the map
const fetchAndDrawHives = async () => { const fetchAndDrawHives = async () => {
try { try {
const data = await fetchHives(); const data = await fetchHives(apiUrl.current);
setHives(data); setHives(data);
drawHives(data); drawHives(data);
} catch (error) { } catch (error) {
@@ -174,7 +181,7 @@ const MapView = () => {
<Popup <Popup
isVisible={popup.visible} isVisible={popup.visible}
coords={popup.coords} coords={popup.coords}
onConfirm={() => moveHives(popup.coords.lat, popup.coords.lon, hives.map(h => h.id))} onConfirm={() => moveHives(apiUrl.current, popup.coords.lat, popup.coords.lon, hives.map(h => h.id))}
onCancel={() => setPopup({ visible: false })} onCancel={() => setPopup({ visible: false })}
/> />
</div> </div>