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()]
|
[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)
|
if (containerName == string.Empty || action == string.Empty)
|
||||||
{
|
{
|
||||||
return BadRequest("Missing required parameter");
|
return BadRequest("Missing required parameter");
|
||||||
}
|
}
|
||||||
|
|
||||||
var container = await _service.GetContainer(containerName, containerNamespace);
|
|
||||||
|
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case "START":
|
case "START":
|
||||||
await _service.StartContainer(container);
|
await _service.StartContainer(containerName, containerNamespace);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "STOP":
|
case "STOP":
|
||||||
await _service.StopContainer(container);
|
await _service.StopContainer(containerName, containerNamespace);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return BadRequest("Incorrect action");
|
return BadRequest("Incorrect action");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(container);
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,6 @@ namespace ContainerDashboard.Models;
|
||||||
public class Container
|
public class Container
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
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;
|
public bool Running { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,6 @@ public interface IContainerHandler
|
||||||
{
|
{
|
||||||
public Task<Container[]> GetContainers();
|
public Task<Container[]> GetContainers();
|
||||||
public Task<Container> GetContainer(string containerName, string? containerNamespace);
|
public Task<Container> GetContainer(string containerName, string? containerNamespace);
|
||||||
public Task StartContainer();
|
public Task StartContainer(string containerName, string? containerNamespace);
|
||||||
public Task StopContainer();
|
public Task StopContainer(string containerName, string? containerNamespace);
|
||||||
}
|
}
|
||||||
|
|
@ -36,11 +36,14 @@ public class KubernetesHandler : IContainerHandler
|
||||||
|
|
||||||
foreach (var item in list.Items)
|
foreach (var item in list.Items)
|
||||||
{
|
{
|
||||||
|
if (item.Namespace() == "kube-system")
|
||||||
|
continue;
|
||||||
|
|
||||||
var c = new Container
|
var c = new Container
|
||||||
{
|
{
|
||||||
containerNamespace = item.Namespace(),
|
ContainerNamespace = item.Namespace(),
|
||||||
Name = item.Name(),
|
Name = item.Name(),
|
||||||
Running = item.Status.Replicas > 0
|
Running = item.Status.ReadyReplicas > 0
|
||||||
};
|
};
|
||||||
|
|
||||||
containers.Add(c);
|
containers.Add(c);
|
||||||
|
|
@ -49,13 +52,57 @@ public class KubernetesHandler : IContainerHandler
|
||||||
return containers.ToArray();
|
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;
|
_handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Container> GetContainer(string containerName, string? containerNamespace)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Container[]> GetContainers()
|
public async Task<Container[]> GetContainers()
|
||||||
{
|
{
|
||||||
return await _handler.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 interface IContainerService
|
||||||
{
|
{
|
||||||
public Task<Container[]> GetContainers();
|
public Task<Container[]> GetContainers();
|
||||||
public Task<Container> GetContainer(string containerName, string? containerNamespace);
|
public Task StartContainer(string containerName, string? containerNamespace);
|
||||||
public Task StartContainer(Container container);
|
public Task StopContainer(string containerName, string? containerNamespace);
|
||||||
public Task StopContainer(Container container);
|
|
||||||
}
|
}
|
||||||
280
frontend/package-lock.json
generated
280
frontend/package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"astro": "^5.16.6",
|
"astro": "^5.16.6",
|
||||||
|
"axios": "^1.13.2",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
|
|
@ -2452,6 +2453,23 @@
|
||||||
"sharp": "^0.34.0"
|
"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": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"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": "^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": {
|
"node_modules/camelcase": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
|
||||||
|
|
@ -2753,6 +2784,18 @@
|
||||||
"node": ">=6"
|
"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": {
|
"node_modules/comma-separated-tokens": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||||
|
|
@ -2940,6 +2983,15 @@
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/dequal": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||||
|
|
@ -3093,6 +3145,20 @@
|
||||||
"node": ">=4"
|
"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": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.267",
|
"version": "1.5.267",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
"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"
|
"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": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||||
|
|
@ -3251,6 +3362,26 @@
|
||||||
"node": ">=8"
|
"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": {
|
"node_modules/fontace": {
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.1.tgz",
|
||||||
|
|
@ -3278,6 +3409,22 @@
|
||||||
"unicode-trie": "^2.0.0"
|
"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": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"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": "^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": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
|
|
@ -3313,12 +3469,61 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/github-slugger": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/h3": {
|
||||||
"version": "1.15.4",
|
"version": "1.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz",
|
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz",
|
||||||
|
|
@ -3336,6 +3541,45 @@
|
||||||
"uncrypto": "^0.1.3"
|
"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": {
|
"node_modules/hast-util-from-html": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
|
"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"
|
"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": {
|
"node_modules/mdast-util-definitions": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
|
"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"
|
"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": {
|
"node_modules/mrmime": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
||||||
|
|
@ -4884,6 +5158,12 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"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": {
|
"node_modules/radix3": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"astro": "^5.16.6",
|
"astro": "^5.16.6",
|
||||||
|
"axios": "^1.13.2",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^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 { useState } from "react";
|
||||||
|
import { StartService } from "../../Lib/StartService";
|
||||||
|
import { StopService } from "../../Lib/StopService";
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
name: string;
|
name: string;
|
||||||
|
namespace?: string;
|
||||||
on: boolean;
|
on: boolean;
|
||||||
image?: string;
|
image?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServiceCard = ({ name, on, image }: props) => {
|
const ServiceCard = ({ name, namespace, on, image }: props) => {
|
||||||
const [isOn, setIsOn] = useState(on);
|
const [isOn, setIsOn] = useState(on);
|
||||||
|
const [status, setStatus] = useState(isOn ? "Running" : "Off");
|
||||||
|
|
||||||
function handleClick() {
|
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 (
|
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">
|
<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>
|
</div>
|
||||||
{image ? <img className="card-image-top" src={image} /> : <div className="card-image-top bg-dark" style={{ width: "18rem", height: "18rem" }} />}
|
{image && <img className="card-image-top" src={image} />}
|
||||||
{/* <div className="card-body">
|
|
||||||
<div className={"btn " + (isOn ? "btn-primary" : "btn-danger")} onClick={handleClick}>{isOn ? "Turn Off" : "Turn On"}</div>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
<div
|
||||||
<div class="container-fluid">
|
style="width: 100%; display: flex; justify-content: center;"
|
||||||
<a class="navbar-brand" href="#">Navbar</a>
|
class="bg-primary text-white"
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
>
|
||||||
<li class="nav-item">
|
<h2 style="margin: 10px 0px;">Container Dashboard</h2>
|
||||||
<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>
|
</div>
|
||||||
</nav>
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
---
|
---
|
||||||
import Welcome from "../components/Welcome.astro";
|
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import ServiceCard from "../components/ServicePage/ServiceCard";
|
import CardList from "../components/ServicePage/CardList";
|
||||||
|
|
||||||
// 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.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Welcome />
|
<CardList client:load />
|
||||||
<ServiceCard name="Test" on={true} client:load />
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue