Initial working prototype
This commit is contained in:
parent
3f6ff87370
commit
ccc5337dc1
17 changed files with 506 additions and 57 deletions
|
|
@ -25,29 +25,27 @@ public class ServiceController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpPost()]
|
||||
public async Task<IActionResult> ToggleContainer(string containerName, string? containerNamespace, string action)
|
||||
public async Task<IActionResult> ToggleContainer([FromQuery] string containerName, string? containerNamespace, string action)
|
||||
{
|
||||
if (containerName == string.Empty || action == string.Empty)
|
||||
{
|
||||
return BadRequest("Missing required parameter");
|
||||
}
|
||||
|
||||
var container = await _service.GetContainer(containerName, containerNamespace);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case "START":
|
||||
await _service.StartContainer(container);
|
||||
await _service.StartContainer(containerName, containerNamespace);
|
||||
break;
|
||||
|
||||
case "STOP":
|
||||
await _service.StopContainer(container);
|
||||
await _service.StopContainer(containerName, containerNamespace);
|
||||
break;
|
||||
|
||||
default:
|
||||
return BadRequest("Incorrect action");
|
||||
}
|
||||
|
||||
return Ok(container);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@ namespace ContainerDashboard.Models;
|
|||
public class Container
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string containerNamespace { get; set; } = string.Empty;
|
||||
public string ContainerNamespace { get; set; } = string.Empty;
|
||||
public bool Running { get; set; } = false;
|
||||
}
|
||||
|
|
@ -6,6 +6,6 @@ public interface IContainerHandler
|
|||
{
|
||||
public Task<Container[]> GetContainers();
|
||||
public Task<Container> GetContainer(string containerName, string? containerNamespace);
|
||||
public Task StartContainer();
|
||||
public Task StopContainer();
|
||||
public Task StartContainer(string containerName, string? containerNamespace);
|
||||
public Task StopContainer(string containerName, string? containerNamespace);
|
||||
}
|
||||
|
|
@ -36,11 +36,14 @@ public class KubernetesHandler : IContainerHandler
|
|||
|
||||
foreach (var item in list.Items)
|
||||
{
|
||||
if (item.Namespace() == "kube-system")
|
||||
continue;
|
||||
|
||||
var c = new Container
|
||||
{
|
||||
containerNamespace = item.Namespace(),
|
||||
ContainerNamespace = item.Namespace(),
|
||||
Name = item.Name(),
|
||||
Running = item.Status.Replicas > 0
|
||||
Running = item.Status.ReadyReplicas > 0
|
||||
};
|
||||
|
||||
containers.Add(c);
|
||||
|
|
@ -49,13 +52,57 @@ public class KubernetesHandler : IContainerHandler
|
|||
return containers.ToArray();
|
||||
}
|
||||
|
||||
public Task StartContainer()
|
||||
public async Task StartContainer(string containerName, string? containerNamespace)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var container = await _client.AppsV1.ReadNamespacedDeploymentAsync(containerName, containerNamespace);
|
||||
|
||||
if (container == null)
|
||||
{
|
||||
throw new Exception($"Could not get container {containerName} in namespace {containerNamespace}");
|
||||
}
|
||||
|
||||
public Task StopContainer()
|
||||
var patchStr = @"
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
""spec"": {
|
||||
""replicas"": 1
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
var patch = new V1Patch(patchStr, V1Patch.PatchType.MergePatch);
|
||||
|
||||
var result = await _client.AppsV1.PatchNamespacedDeploymentAsync(patch, containerName, containerNamespace, fieldManager: "ContainerDashboard");
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new Exception($"Could not update container {containerName} in namespace {containerNamespace}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopContainer(string containerName, string? containerNamespace)
|
||||
{
|
||||
var container = await _client.AppsV1.ReadNamespacedDeploymentAsync(containerName, containerNamespace);
|
||||
|
||||
if (container == null)
|
||||
{
|
||||
throw new Exception($"Could not get container {containerName} in namespace {containerNamespace}");
|
||||
}
|
||||
|
||||
var patchStr = @"
|
||||
{
|
||||
""spec"": {
|
||||
""replicas"": 0
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
var patch = new V1Patch(patchStr, V1Patch.PatchType.MergePatch);
|
||||
|
||||
var result = await _client.AppsV1.PatchNamespacedDeploymentAsync(patch, containerName, containerNamespace, fieldManager: "ContainerDashboard");
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new Exception($"Could not update container {containerName} in namespace {containerNamespace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,23 +12,18 @@ public class ContainerService : IContainerService
|
|||
_handler = handler;
|
||||
}
|
||||
|
||||
public Task<Container> GetContainer(string containerName, string? containerNamespace)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<Container[]> GetContainers()
|
||||
{
|
||||
return await _handler.GetContainers();
|
||||
}
|
||||
|
||||
public Task StartContainer(Container container)
|
||||
public async Task StartContainer(string containerName, string? containerNamespace)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
await _handler.StartContainer(containerName, containerNamespace);
|
||||
}
|
||||
|
||||
public Task StopContainer(Container container)
|
||||
public async Task StopContainer(string containerName, string? containerNamespace)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
await _handler.StopContainer(containerName, containerNamespace);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ namespace ContainerDashboard.Services;
|
|||
public interface IContainerService
|
||||
{
|
||||
public Task<Container[]> GetContainers();
|
||||
public Task<Container> GetContainer(string containerName, string? containerNamespace);
|
||||
public Task StartContainer(Container container);
|
||||
public Task StopContainer(Container container);
|
||||
public Task StartContainer(string containerName, string? containerNamespace);
|
||||
public Task StopContainer(string containerName, string? containerNamespace);
|
||||
}
|
||||
280
frontend/package-lock.json
generated
280
frontend/package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
|||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"astro": "^5.16.6",
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
|
|
@ -2452,6 +2453,23 @@
|
|||
"sharp": "^0.34.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
|
|
@ -2609,6 +2627,19 @@
|
|||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
|
||||
|
|
@ -2753,6 +2784,18 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||
|
|
@ -2940,6 +2983,15 @@
|
|||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
|
|
@ -3093,6 +3145,20 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.267",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||
|
|
@ -3117,12 +3183,57 @@
|
|||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||
|
|
@ -3251,6 +3362,26 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fontace": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.1.tgz",
|
||||
|
|
@ -3278,6 +3409,22 @@
|
|||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
|
|
@ -3292,6 +3439,15 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
|
|
@ -3313,12 +3469,61 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/github-slugger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/h3": {
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz",
|
||||
|
|
@ -3336,6 +3541,45 @@
|
|||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-html": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
|
||||
|
|
@ -3759,6 +4003,15 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-definitions": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
|
||||
|
|
@ -4580,6 +4833,27 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mrmime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
||||
|
|
@ -4884,6 +5158,12 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/radix3": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"astro": "^5.16.6",
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
|
|
|
|||
7
frontend/src/Lib/Config.ts
Normal file
7
frontend/src/Lib/Config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface ConfigType {
|
||||
endpoint: string;
|
||||
}
|
||||
|
||||
export const Config: ConfigType = {
|
||||
endpoint: "http://localhost:5107/Service",
|
||||
}
|
||||
15
frontend/src/Lib/ListServices.ts
Normal file
15
frontend/src/Lib/ListServices.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import axios from "axios";
|
||||
import type { Service } from "./Models/Service";
|
||||
import { Config } from "./Config";
|
||||
|
||||
export const FetchServices = async () => {
|
||||
const response = await axios.get(Config.endpoint);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`Error fetching services from database: ${response.status} - ${response.statusText}: ${response.data}`);
|
||||
}
|
||||
|
||||
const data = response.data as Array<Service>;
|
||||
|
||||
return data;
|
||||
}
|
||||
5
frontend/src/Lib/Models/Service.ts
Normal file
5
frontend/src/Lib/Models/Service.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export interface Service {
|
||||
containerNamespace: string;
|
||||
name: string;
|
||||
running: boolean;
|
||||
}
|
||||
11
frontend/src/Lib/StartService.ts
Normal file
11
frontend/src/Lib/StartService.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import axios from "axios";
|
||||
import type { Service } from "./Models/Service";
|
||||
import { Config } from "./Config";
|
||||
|
||||
export const StartService = async (serviceName: string, serviceNamespace?: string) => {
|
||||
const response = await axios.post(`${Config.endpoint}?containerName=${serviceName}&containerNamespace=${serviceNamespace}&action=START`);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`Error starting service "${serviceName}": ${response.status} - ${response.statusText}: ${response.data}`);
|
||||
}
|
||||
}
|
||||
11
frontend/src/Lib/StopService.ts
Normal file
11
frontend/src/Lib/StopService.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import axios from "axios";
|
||||
import type { Service } from "./Models/Service";
|
||||
import { Config } from "./Config";
|
||||
|
||||
export const StopService = async (serviceName: string, serviceNamespace?: string) => {
|
||||
const response = await axios.post(`${Config.endpoint}?containerName=${serviceName}&containerNamespace=${serviceNamespace}&action=STOP`);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`Error starting service "${serviceName}": ${response.status} - ${response.statusText}: ${response.data}`);
|
||||
}
|
||||
}
|
||||
66
frontend/src/components/ServicePage/CardList.tsx
Normal file
66
frontend/src/components/ServicePage/CardList.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { FetchServices } from "../../Lib/ListServices";
|
||||
import type { Service } from "../../Lib/Models/Service";
|
||||
import ServiceCard from "./ServiceCard";
|
||||
|
||||
interface sortedServices {
|
||||
name: string;
|
||||
services: Array<Service>;
|
||||
}
|
||||
|
||||
const CardList = () => {
|
||||
const [services, setServices] = useState<Array<sortedServices>>([]);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
FetchServices().then(response => {
|
||||
processServices(response);
|
||||
}).catch(e => {
|
||||
setError(e)
|
||||
})
|
||||
}, []);
|
||||
|
||||
function processServices(response: Array<Service>) {
|
||||
const namespaces: Array<sortedServices> = [];
|
||||
|
||||
response.forEach(service => {
|
||||
let namespaceGroup = namespaces.find((a) => a.name === service.containerNamespace);
|
||||
|
||||
if (!namespaceGroup) {
|
||||
namespaceGroup = { name: service.containerNamespace, services: [] };
|
||||
namespaces.push(namespaceGroup);
|
||||
}
|
||||
|
||||
namespaceGroup.services.push(service);
|
||||
});
|
||||
|
||||
setServices(namespaces);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{error && <><h2>There was an error fetching the container list</h2> <p>{error}</p></>}
|
||||
{services && services.map(namespace => {
|
||||
return (
|
||||
<div key={namespace.name}>
|
||||
<div style={{ width: "100%", height: "60px", display: "flex", alignItems: "center" }} className="bg-secondary text-white">
|
||||
<h3 style={{ marginLeft: "30px", marginBottom: "0px" }}>{namespace.name}</h3>
|
||||
</div>
|
||||
<div style={{ display: "flex", width: "100%", flexWrap: "wrap" }}>
|
||||
{namespace.services.map(service => {
|
||||
return (<ServiceCard name={service.name} namespace={service.containerNamespace} on={service.running} key={service.name + " " + service.containerNamespace} />)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{/* {services && services.map(service => {
|
||||
return (
|
||||
<ServiceCard name={service.name} namespace={service.containerNamespace} on={service.running} key={service.name + " " + service.containerNamespace} />
|
||||
)
|
||||
})} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardList;
|
||||
|
|
@ -1,28 +1,56 @@
|
|||
import { useState } from "react";
|
||||
import { StartService } from "../../Lib/StartService";
|
||||
import { StopService } from "../../Lib/StopService";
|
||||
|
||||
interface props {
|
||||
name: string;
|
||||
namespace?: string;
|
||||
on: boolean;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
const ServiceCard = ({ name, on, image }: props) => {
|
||||
const ServiceCard = ({ name, namespace, on, image }: props) => {
|
||||
const [isOn, setIsOn] = useState(on);
|
||||
const [status, setStatus] = useState(isOn ? "Running" : "Off");
|
||||
|
||||
function handleClick() {
|
||||
setIsOn(!isOn);
|
||||
if (isOn) {
|
||||
stopService();
|
||||
} else {
|
||||
startService();
|
||||
}
|
||||
}
|
||||
|
||||
function startService() {
|
||||
setStatus("Starting...");
|
||||
StartService(name, namespace).then(() => {
|
||||
setIsOn(true);
|
||||
}).catch(e => {
|
||||
alert(`There was an error starting the service: ${e}`);
|
||||
}).finally(() => {
|
||||
setStatus("Running");
|
||||
})
|
||||
}
|
||||
|
||||
function stopService() {
|
||||
setStatus("Stopping...");
|
||||
StopService(name, namespace).then(() => {
|
||||
setIsOn(false);
|
||||
}).catch(e => {
|
||||
alert(`There was an error stopping the service: ${e}`);
|
||||
}).finally(() => {
|
||||
setStatus("Off");
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={"card " + (isOn ? "bg-primary" : "bg-danger")} style={{ width: "18rem;", margin: "20px" }} onClick={handleClick}>
|
||||
<div className={"card text-white " + (isOn ? "bg-success" : "bg-danger")} style={{ width: "18rem", margin: "20px" }} onClick={handleClick}>
|
||||
<div className="card-header">
|
||||
<h5 className="card-title">{name + " - " + (isOn ? "Running" : "Off")}</h5>
|
||||
<h5 className="card-title">{name}</h5>
|
||||
<h6 className="card-title">{status}</h6>
|
||||
</div>
|
||||
{image ? <img className="card-image-top" src={image} /> : <div className="card-image-top bg-dark" style={{ width: "18rem", height: "18rem" }} />}
|
||||
{/* <div className="card-body">
|
||||
<div className={"btn " + (isOn ? "btn-primary" : "btn-danger")} onClick={handleClick}>{isOn ? "Turn Off" : "Turn On"}</div>
|
||||
</div> */}
|
||||
{image && <img className="card-image-top" src={image} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">Navbar</a>
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">bwa</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">bwa</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div
|
||||
style="width: 100%; display: flex; justify-content: center;"
|
||||
class="bg-primary text-white"
|
||||
>
|
||||
<h2 style="margin: 10px 0px;">Container Dashboard</h2>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
---
|
||||
import Welcome from "../components/Welcome.astro";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import ServiceCard from "../components/ServicePage/ServiceCard";
|
||||
|
||||
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
|
||||
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
|
||||
import CardList from "../components/ServicePage/CardList";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Welcome />
|
||||
<ServiceCard name="Test" on={true} client:load />
|
||||
<CardList client:load />
|
||||
</Layout>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue