diff --git a/App/Backend/cmd/kubernetes/handlers/GetConfigMaps.go b/App/Backend/cmd/kubernetes/handlers/GetConfigMaps.go index f094045..e7f613d 100644 --- a/App/Backend/cmd/kubernetes/handlers/GetConfigMaps.go +++ b/App/Backend/cmd/kubernetes/handlers/GetConfigMaps.go @@ -4,21 +4,24 @@ import ( "context" "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" "github.com/gin-gonic/gin" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" + "sync" "time" ) func GetConfigMapsHandler(ctx *gin.Context) { namespace, ok := ctx.GetQuery("namespace") + pageSize, pageToken := GetPageSizeAndPageToken(ctx) - var configMapList *[]kubernetes.ConfigMap + var configMapList *PaginatedResponse[[]kubernetes.ConfigMap] var err error if ok { - configMapList, err = GetConfigMaps(c, namespace) + configMapList, err = GetConfigMaps(c, namespace, pageSize, pageToken) } else { - configMapList, err = GetConfigMaps(c, "") + configMapList, err = GetConfigMaps(c, "", pageSize, pageToken) } if err != nil { @@ -28,19 +31,42 @@ func GetConfigMapsHandler(ctx *gin.Context) { ctx.JSON(http.StatusOK, configMapList) } -func GetConfigMaps(c *kubernetes.IClient, namespace string) (*[]kubernetes.ConfigMap, error) { +func GetConfigMaps(c *kubernetes.IClient, namespace string, pageSize int, pageToken string) (*PaginatedResponse[[]kubernetes.ConfigMap], error) { ct, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - list, err := (*c).GetConfigMaps(namespace).List(ct, metav1.ListOptions{}) + list, err := (*c).GetConfigMaps(namespace).List(ct, metav1.ListOptions{ + Limit: int64(pageSize), + Continue: pageToken, + }) if err != nil { return nil, err } - var configMapList = make([]kubernetes.ConfigMap, len(list.Items)) + return &PaginatedResponse[[]kubernetes.ConfigMap]{ + Response: transformConfigMaps(&list.Items), + PageToken: list.Continue, + }, nil +} + +func transformConfigMaps(list *[]v1.ConfigMap) []kubernetes.ConfigMap { + var configList = make([]kubernetes.ConfigMap, len(*list)) + + var wg sync.WaitGroup + concurrency := 20 + semaphore := make(chan struct{}, concurrency) - for i, configMap := range list.Items { - configMapList[i] = kubernetes.NewConfigMap(configMap) + for i, config := range *list { + wg.Add(1) + semaphore <- struct{}{} + + go func(i int, node v1.ConfigMap) { + defer wg.Done() + configList[i] = kubernetes.NewConfigMap(config) + <-semaphore + }(i, config) } - return &configMapList, nil + + wg.Wait() + return configList } diff --git a/App/Backend/cmd/kubernetes/handlers/GetDeployments.go b/App/Backend/cmd/kubernetes/handlers/GetDeployments.go index b82e5e5..fe0403c 100644 --- a/App/Backend/cmd/kubernetes/handlers/GetDeployments.go +++ b/App/Backend/cmd/kubernetes/handlers/GetDeployments.go @@ -4,22 +4,24 @@ import ( "context" "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" "github.com/gin-gonic/gin" + v1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" + "sync" "time" ) func GetDeploymentsHandler(ctx *gin.Context) { - namespace, ok := ctx.GetQuery("namespace") + pageSize, pageToken := GetPageSizeAndPageToken(ctx) - var deploymentList *[]kubernetes.Deployment + var deploymentList *PaginatedResponse[[]kubernetes.Deployment] var err error if ok { - deploymentList, err = GetDeployments(c, namespace) + deploymentList, err = GetDeployments(c, namespace, pageSize, pageToken) } else { - deploymentList, err = GetDeployments(c, "") + deploymentList, err = GetDeployments(c, "", pageSize, pageToken) } if err != nil { @@ -29,19 +31,42 @@ func GetDeploymentsHandler(ctx *gin.Context) { ctx.JSON(http.StatusOK, deploymentList) } -func GetDeployments(c *kubernetes.IClient, namespace string) (*[]kubernetes.Deployment, error) { +func GetDeployments(c *kubernetes.IClient, namespace string, pageSize int, pageToken string) (*PaginatedResponse[[]kubernetes.Deployment], error) { ct, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - list, err := (*c).GetDeployments(namespace).List(ct, metav1.ListOptions{}) + list, err := (*c).GetDeployments(namespace).List(ct, metav1.ListOptions{ + Limit: int64(pageSize), + Continue: pageToken, + }) if err != nil { return nil, err } - var deploymentList = make([]kubernetes.Deployment, len(list.Items)) + return &PaginatedResponse[[]kubernetes.Deployment]{ + Response: transformDeployments(&list.Items), + PageToken: list.Continue, + }, nil +} + +func transformDeployments(list *[]v1.Deployment) []kubernetes.Deployment { + var deploymentList = make([]kubernetes.Deployment, len(*list)) - for i, deployment := range list.Items { - deploymentList[i] = kubernetes.NewDeployment(deployment) + var wg sync.WaitGroup + concurrency := 20 + semaphore := make(chan struct{}, concurrency) + + for i, deployment := range *list { + wg.Add(1) + semaphore <- struct{}{} + + go func(i int, node v1.Deployment) { + defer wg.Done() + deploymentList[i] = kubernetes.NewDeployment(deployment) + <-semaphore + }(i, deployment) } - return &deploymentList, nil + + wg.Wait() + return deploymentList } diff --git a/App/Backend/cmd/kubernetes/handlers/GetNodes.go b/App/Backend/cmd/kubernetes/handlers/GetNodes.go index 7eca921..ded3c24 100644 --- a/App/Backend/cmd/kubernetes/handlers/GetNodes.go +++ b/App/Backend/cmd/kubernetes/handlers/GetNodes.go @@ -4,13 +4,17 @@ import ( "context" "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" "github.com/gin-gonic/gin" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" + "sync" "time" ) func GetNodesHandler(ctx *gin.Context) { - nodeList, err := GetNodes(c) + pageSize, pageToken := GetPageSizeAndPageToken(ctx) + + nodeList, err := GetNodes(c, pageSize, pageToken) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "An error has occurred or the request has been timed out."}) return @@ -18,19 +22,42 @@ func GetNodesHandler(ctx *gin.Context) { ctx.JSON(http.StatusOK, nodeList) } -func GetNodes(c *kubernetes.IClient) (*[]kubernetes.Node, error) { +func GetNodes(c *kubernetes.IClient, pageSize int, pageToken string) (*PaginatedResponse[[]kubernetes.Node], error) { ct, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - list, err := (*c).GetNodes().List(ct, metav1.ListOptions{}) + list, err := (*c).GetNodes().List(ct, metav1.ListOptions{ + Limit: int64(pageSize), + Continue: pageToken, + }) if err != nil { return nil, err } - var nodeList = make([]kubernetes.Node, len(list.Items)) + return &PaginatedResponse[[]kubernetes.Node]{ + Response: transformNodes(&list.Items), + PageToken: list.Continue, + }, nil +} + +func transformNodes(list *[]v1.Node) []kubernetes.Node { + var nodeList = make([]kubernetes.Node, len(*list)) - for i, node := range list.Items { - nodeList[i] = kubernetes.NewNode(node, c) + var wg sync.WaitGroup + concurrency := 20 + semaphore := make(chan struct{}, concurrency) + + for i, node := range *list { + wg.Add(1) + semaphore <- struct{}{} + + go func(i int, node v1.Node) { + defer wg.Done() + nodeList[i] = kubernetes.NewNode(node, c) + <-semaphore + }(i, node) } - return &nodeList, nil + + wg.Wait() + return nodeList } diff --git a/App/Backend/cmd/kubernetes/handlers/GetPods.go b/App/Backend/cmd/kubernetes/handlers/GetPods.go index fcaf97f..970ff2f 100644 --- a/App/Backend/cmd/kubernetes/handlers/GetPods.go +++ b/App/Backend/cmd/kubernetes/handlers/GetPods.go @@ -4,21 +4,24 @@ import ( "context" "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" "github.com/gin-gonic/gin" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" + "sync" "time" ) func GetPodsHandler(ctx *gin.Context) { namespace, ok := ctx.GetQuery("namespace") + pageSize, pageToken := GetPageSizeAndPageToken(ctx) - var podList *[]kubernetes.Pod + var podList *PaginatedResponse[[]kubernetes.Pod] var err error if ok { - podList, err = GetPods(c, namespace) + podList, err = GetPods(c, namespace, pageSize, pageToken) } else { - podList, err = GetPods(c, "") + podList, err = GetPods(c, "", pageSize, pageToken) } if err != nil { @@ -28,19 +31,42 @@ func GetPodsHandler(ctx *gin.Context) { ctx.JSON(http.StatusOK, podList) } -func GetPods(c *kubernetes.IClient, namespace string) (*[]kubernetes.Pod, error) { +func GetPods(c *kubernetes.IClient, namespace string, pageSize int, continueToken string) (*PaginatedResponse[[]kubernetes.Pod], error) { ct, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - list, err := (*c).GetPods(namespace).List(ct, metav1.ListOptions{}) + list, err := (*c).GetPods(namespace).List(ct, metav1.ListOptions{ + Limit: int64(pageSize), + Continue: continueToken, + }) if err != nil { return nil, err } - var podList = make([]kubernetes.Pod, len(list.Items)) + return &PaginatedResponse[[]kubernetes.Pod]{ + Response: transformPods(&list.Items), + PageToken: list.Continue, + }, nil +} + +func transformPods(list *[]v1.Pod) []kubernetes.Pod { + var podList = make([]kubernetes.Pod, len(*list)) + + var wg sync.WaitGroup + concurrency := 20 + semaphore := make(chan struct{}, concurrency) - for i, pod := range list.Items { - podList[i] = kubernetes.NewPod(pod, c) + for i, pod := range *list { + wg.Add(1) + semaphore <- struct{}{} + + go func(i int, pod v1.Pod) { + defer wg.Done() + podList[i] = kubernetes.NewPod(pod, c) + <-semaphore + }(i, pod) } - return &podList, nil + + wg.Wait() + return podList } diff --git a/App/Backend/cmd/kubernetes/handlers/GetSecrets.go b/App/Backend/cmd/kubernetes/handlers/GetSecrets.go index 0ae6e64..7edb731 100644 --- a/App/Backend/cmd/kubernetes/handlers/GetSecrets.go +++ b/App/Backend/cmd/kubernetes/handlers/GetSecrets.go @@ -4,21 +4,24 @@ import ( "context" "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" "github.com/gin-gonic/gin" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" + "sync" "time" ) func GetSecretsHandler(ctx *gin.Context) { namespace, ok := ctx.GetQuery("namespace") + pageSize, pageToken := GetPageSizeAndPageToken(ctx) - var secretList *[]kubernetes.Secret + var secretList *PaginatedResponse[[]kubernetes.Secret] var err error if ok { - secretList, err = GetSecrets(c, namespace) + secretList, err = GetSecrets(c, namespace, pageSize, pageToken) } else { - secretList, err = GetSecrets(c, "") + secretList, err = GetSecrets(c, "", pageSize, pageToken) } if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "An error has occurred or the request has been timed out."}) @@ -27,19 +30,43 @@ func GetSecretsHandler(ctx *gin.Context) { ctx.JSON(http.StatusOK, secretList) } -func GetSecrets(c *kubernetes.IClient, namespace string) (*[]kubernetes.Secret, error) { +func GetSecrets(c *kubernetes.IClient, namespace string, pageSize int, pageToken string) (*PaginatedResponse[[]kubernetes.Secret], error) { ct, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - list, err := (*c).GetSecrets(namespace).List(ct, metav1.ListOptions{}) + list, err := (*c).GetSecrets(namespace).List(ct, metav1.ListOptions{ + Limit: int64(pageSize), + Continue: pageToken, + }) if err != nil { return nil, err } - var secretList = make([]kubernetes.Secret, len(list.Items)) + return &PaginatedResponse[[]kubernetes.Secret]{ + Response: transformSecrets(&list.Items), + PageToken: list.Continue, + }, nil +} + +func transformSecrets(list *[]v1.Secret) []kubernetes.Secret { + var secretList = make([]kubernetes.Secret, len(*list)) + + var wg sync.WaitGroup + concurrency := 20 + semaphore := make(chan struct{}, concurrency) + + for i, secret := range *list { + wg.Add(1) + semaphore <- struct{}{} + + go func(i int, node v1.Secret) { + defer wg.Done() + secretList[i] = kubernetes.NewSecret(secret) + <-semaphore + }(i, secret) - for i, secret := range list.Items { - secretList[i] = kubernetes.NewSecret(secret) } - return &secretList, nil + wg.Wait() + + return secretList } diff --git a/App/Backend/cmd/kubernetes/handlers/GetServices.go b/App/Backend/cmd/kubernetes/handlers/GetServices.go index a4f6366..82ed702 100644 --- a/App/Backend/cmd/kubernetes/handlers/GetServices.go +++ b/App/Backend/cmd/kubernetes/handlers/GetServices.go @@ -4,21 +4,24 @@ import ( "context" "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" "github.com/gin-gonic/gin" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" + "sync" "time" ) func GetServicesHandler(ctx *gin.Context) { namespace, ok := ctx.GetQuery("namespace") + pageSize, pageToken := GetPageSizeAndPageToken(ctx) - var serviceList *[]kubernetes.Service + var serviceList *PaginatedResponse[[]kubernetes.Service] var err error if ok { - serviceList, err = GetServices(c, namespace) + serviceList, err = GetServices(c, namespace, pageSize, pageToken) } else { - serviceList, err = GetServices(c, "") + serviceList, err = GetServices(c, "", pageSize, pageToken) } if err != nil { @@ -28,19 +31,40 @@ func GetServicesHandler(ctx *gin.Context) { ctx.JSON(http.StatusOK, serviceList) } -func GetServices(c *kubernetes.IClient, namespace string) (*[]kubernetes.Service, error) { +func GetServices(c *kubernetes.IClient, namespace string, pageSize int, pageToken string) (*PaginatedResponse[[]kubernetes.Service], error) { ct, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - list, err := (*c).GetServices(namespace).List(ct, metav1.ListOptions{}) + list, err := (*c).GetServices(namespace).List(ct, metav1.ListOptions{ + Limit: int64(pageSize), + Continue: pageToken, + }) if err != nil { return nil, err } - var serviceList = make([]kubernetes.Service, len(list.Items)) + return &PaginatedResponse[[]kubernetes.Service]{ + Response: transformServices(&list.Items), + PageToken: list.Continue, + }, nil +} + +func transformServices(list *[]v1.Service) []kubernetes.Service { + var serviceList = make([]kubernetes.Service, len(*list)) + var wg sync.WaitGroup + concurrency := 20 + semaphore := make(chan struct{}, concurrency) - for i, service := range list.Items { - serviceList[i] = kubernetes.NewService(service) + for i, service := range *list { + wg.Add(1) + + go func(i int, service v1.Service) { + defer wg.Done() + serviceList[i] = kubernetes.NewService(service) + semaphore <- struct{}{} + }(i, service) } - return &serviceList, nil + wg.Wait() + + return serviceList } diff --git a/App/Backend/cmd/kubernetes/handlers/Vars.go b/App/Backend/cmd/kubernetes/handlers/Vars.go deleted file mode 100644 index b1bcf43..0000000 --- a/App/Backend/cmd/kubernetes/handlers/Vars.go +++ /dev/null @@ -1,5 +0,0 @@ -package handlers - -import "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" - -var c = kubernetes.GetClients() diff --git a/App/Backend/cmd/kubernetes/handlers/util.go b/App/Backend/cmd/kubernetes/handlers/util.go new file mode 100644 index 0000000..c504015 --- /dev/null +++ b/App/Backend/cmd/kubernetes/handlers/util.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "github.com/eliasdehondt/K10s/App/Backend/cmd/kubernetes" + "github.com/gin-gonic/gin" + "strconv" +) + +var c = kubernetes.GetClients() + +type PaginatedResponse[T any] struct { + Response T + PageToken string +} + +func GetPageSizeAndPageToken(ctx *gin.Context) (int, string) { + pageSizeString, _ := ctx.GetQuery("pageSize") + pageToken, _ := ctx.GetQuery("pageToken") + pageSize, err := strconv.Atoi(pageSizeString) + if err != nil { + pageSize = 20 + } + return pageSize, pageToken +} diff --git a/App/Backend/testing/get_handler_test.go b/App/Backend/testing/get_handler_test.go index 2073f9c..c2380dc 100644 --- a/App/Backend/testing/get_handler_test.go +++ b/App/Backend/testing/get_handler_test.go @@ -10,93 +10,93 @@ import ( var client = kubernetes.TestFakeClient() func TestGetNodes(t *testing.T) { - nodes, err := handlers.GetNodes(&client) + nodes, err := handlers.GetNodes(&client, 20, "") assert.NoError(t, err) assert.NotEmpty(t, nodes) - assert.Equal(t, "node-1", (*nodes)[0].Name) - assert.Equal(t, "node-2", (*nodes)[1].Name) + assert.Equal(t, "node-1", (*nodes).Response[0].Name) + assert.Equal(t, "node-2", (*nodes).Response[1].Name) } func TestGetPods(t *testing.T) { - pods, err := handlers.GetPods(&client, "") + pods, err := handlers.GetPods(&client, "", 20, "") assert.NoError(t, err) assert.NotEmpty(t, pods) - assert.Equal(t, "pod-1", (*pods)[0].Name) - assert.Equal(t, "pod-2", (*pods)[1].Name) + assert.Equal(t, "pod-1", (*pods).Response[0].Name) + assert.Equal(t, "pod-2", (*pods).Response[1].Name) } func TestGetPodsWithNamespace(t *testing.T) { - pods, err := handlers.GetPods(&client, "test") + pods, err := handlers.GetPods(&client, "test", 20, "") assert.NoError(t, err) assert.NotEmpty(t, pods) - assert.Equal(t, "pod-3", (*pods)[0].Name) + assert.Equal(t, "pod-3", (*pods).Response[0].Name) } func TestGetServices(t *testing.T) { - services, err := handlers.GetServices(&client, "") + services, err := handlers.GetServices(&client, "", 20, "") assert.NoError(t, err) assert.NotEmpty(t, services) - assert.Equal(t, "service-1", (*services)[0].Name) + assert.Equal(t, "service-1", (*services).Response[0].Name) } func TestGetServicesWithNamespace(t *testing.T) { - services, err := handlers.GetServices(&client, "test") + services, err := handlers.GetServices(&client, "test", 20, "") assert.NoError(t, err) assert.NotEmpty(t, services) - assert.Equal(t, "service-2", (*services)[0].Name) + assert.Equal(t, "service-2", (*services).Response[0].Name) } func TestGetDeployments(t *testing.T) { - deployments, err := handlers.GetDeployments(&client, "") + deployments, err := handlers.GetDeployments(&client, "", 20, "") assert.NoError(t, err) assert.NotEmpty(t, deployments) - assert.Equal(t, "deployment-1", (*deployments)[0].Name) + assert.Equal(t, "deployment-1", (*deployments).Response[0].Name) } func TestGetDeploymentsWithNamespace(t *testing.T) { - deployments, err := handlers.GetDeployments(&client, "test") + deployments, err := handlers.GetDeployments(&client, "test", 20, "") assert.NoError(t, err) assert.NotEmpty(t, deployments) - assert.Equal(t, "deployment-2", (*deployments)[0].Name) + assert.Equal(t, "deployment-2", (*deployments).Response[0].Name) } func TestGetConfigMaps(t *testing.T) { - maps, err := handlers.GetConfigMaps(&client, "") + maps, err := handlers.GetConfigMaps(&client, "", 20, "") assert.NoError(t, err) assert.NotEmpty(t, maps) - assert.Equal(t, "configmap-1", (*maps)[0].Name) + assert.Equal(t, "configmap-1", (*maps).Response[0].Name) } func TestGetConfigMapsWithNamespace(t *testing.T) { - maps, err := handlers.GetConfigMaps(&client, "test") + maps, err := handlers.GetConfigMaps(&client, "test", 20, "") assert.NoError(t, err) assert.NotEmpty(t, maps) - assert.Equal(t, "configmap-2", (*maps)[0].Name) + assert.Equal(t, "configmap-2", (*maps).Response[0].Name) } func TestGetSecrets(t *testing.T) { - secrets, err := handlers.GetSecrets(&client, "") + secrets, err := handlers.GetSecrets(&client, "", 20, "") assert.NoError(t, err) assert.NotEmpty(t, secrets) - assert.Equal(t, "secret-1", (*secrets)[0].Name) + assert.Equal(t, "secret-1", (*secrets).Response[0].Name) } func TestGetSecretsWithNamespace(t *testing.T) { - secrets, err := handlers.GetSecrets(&client, "test") + secrets, err := handlers.GetSecrets(&client, "test", 20, "") assert.NoError(t, err) assert.NotEmpty(t, secrets) - assert.Equal(t, "secret-2", (*secrets)[0].Name) + assert.Equal(t, "secret-2", (*secrets).Response[0].Name) } func TestGetTotalUsage(t *testing.T) { diff --git a/App/Frontend/src/app/add/add.component.css b/App/Frontend/src/app/add/add.component.css new file mode 100644 index 0000000..666fa9d --- /dev/null +++ b/App/Frontend/src/app/add/add.component.css @@ -0,0 +1,185 @@ +/**********************************/ +/* @since 01/01/2025 */ +/* @author K10s Open Source Team */ +/**********************************/ + +/* SetUp Grid */ +.add-header { grid-area: header; } +.nav { grid-area: nav; } +.add-main { grid-area: main; } +.footer { grid-area: footer; } + +.add-body { + display: grid; + grid-template-areas: + 'nav header' + 'nav main' + 'footer footer'; + grid-template-columns: 1fr 9fr; + grid-template-rows: 1fr 10fr 0.5fr; + gap: 16px; + padding: 10px; + height: 95vh; +} +/* SetUp Grid */ + +.add-header { + background-color: var(--quaternary); + color: var(--text); + border-radius: var(--radius); + box-shadow: 2px 0 5px var(--shadow); + width: 100%; + height: 100%; + font-weight: bold; + font-size: 1rem; + display: flex; + align-items: center; + justify-content: center; +} + +/* Add Main */ +.add-main { + background-color: var(--tertiary); + color: var(--text); + border-radius: var(--radius); + box-shadow: 2px 0 5px var(--shadow); + width: 100%; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + gap: 8vh; +} + +.add-svg-yaml { + width: 28px; + fill: var(--text); +} + +.add-textarea-container { + position: relative; + width: 100%; + height: 80%; + display: flex; +} + +.add-textarea { + width: 100%; + height: 100%; + padding: var(--spacing); + background-color: var(--tertiary); + color: var(--text); + border-radius: var(--radius); + box-shadow: 2px 0 5px var(--shadow); + overflow: hidden; +} + +.add-clear-button { + position: absolute; + top: 10px; + right: 10px; + background: var(--secondary); + color: white; + border: none; + width: 25px; + height: 25px; + border-radius: 50%; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.3s ease; +} + +.add-clear-button:hover { + background: var(--primary); +} + +.add-button { + width: 10%; + padding: 12px 15px; + border: 1px solid var(--secondary); + border-radius: var(--radius); + background-color: var(--secondary); + color: var(--text); + font-size: 16px; + font-weight: bold; + text-align: center; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.add-button:hover{ + background-color: var(--primary); + border-color: var(--primary); +} + +.add-yaml-button { + width: 15%; + border: 1px solid var(--secondary); + border-radius: var(--radius); + background-color: var(--secondary); + color: var(--text); + font-size: 16px; + font-weight: bold; + text-align: center; + transition: background-color 0.3s ease, border-color 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.add-yaml-button:hover { + background-color: var(--primary); + border-color: var(--primary); +} +/* Add Main */ + +/* Media Queries */ +@media screen and (max-width: 1360px) { + .add-body { + grid-template-areas: + 'nav header' + 'nav main' + 'footer footer'; + grid-template-columns: 0.5fr 3fr; + grid-template-rows: 2fr 9fr 0.5fr; + } + + .add-header { + grid-template-areas: + 'header1 header2' + 'header3 header3'; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + } +} + +@media screen and (max-width: 1045px) { + .add-header { + grid-template-areas: + 'header1' + 'header2' + 'header3'; + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr 1fr; + } +} + +@media screen and (max-width: 610px) { + .add-body { + grid-template-areas: + 'nav' + 'main' + 'footer'; + grid-template-columns: 1fr; + grid-template-rows: 2fr 9fr 0.5fr; + } + + .add-header { + display: none; + } +} \ No newline at end of file diff --git a/App/Frontend/src/app/add/add.component.html b/App/Frontend/src/app/add/add.component.html new file mode 100644 index 0000000..091c928 --- /dev/null +++ b/App/Frontend/src/app/add/add.component.html @@ -0,0 +1,22 @@ + + +
+ + + +
+ +
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/App/Frontend/src/app/add/add.component.ts b/App/Frontend/src/app/add/add.component.ts new file mode 100644 index 0000000..69645b8 --- /dev/null +++ b/App/Frontend/src/app/add/add.component.ts @@ -0,0 +1,71 @@ +/**********************************/ +/* @since 01/01/2025 */ +/* @author K10s Open Source Team */ +/**********************************/ + +import { Component } from '@angular/core'; +import { NavComponent } from "../nav/nav.component"; +import { FooterComponent } from "../footer/footer.component"; +import { FormsModule } from "@angular/forms"; +import { AddService } from "../services/add.service"; +import { TranslatePipe } from "@ngx-translate/core"; + +@Component({ + selector: 'app-add', + templateUrl: './add.component.html', + styleUrl: './add.component.css', + imports: [NavComponent, FooterComponent, FormsModule, TranslatePipe], + standalone: true +}) + +export class AddComponent { + yamlText: string = ''; + fileContent: string | null = null; + fileUploaded: boolean = false; + textAreaActive: boolean = false; + + constructor(private addService: AddService) {} + + onFileUpload(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + const file = input.files[0]; + + const reader = new FileReader(); + reader.onload = (e) => { + this.fileContent = e.target?.result as string; + this.fileUploaded = true; + this.clearTextarea(); + this.textAreaActive = false; + }; + reader.readAsText(file); + } + } + + onTextInput() { + this.fileUploaded = false; + this.fileContent = null; + this.textAreaActive = true; + } + + sendData() { + const yamlData = this.fileContent || this.yamlText; + + if (!yamlData) { + console.warn('No YAML data to upload'); + return; + } + + this.addService.uploadYaml(yamlData).subscribe({ + next: (response) => {console.log('YAML upload success', response); this.clearTextarea();}, + error: (error) => console.error('Upload failed', error), + }); + } + + clearTextarea() { + this.yamlText = ''; + this.fileUploaded = true; + this.fileContent = null; + this.textAreaActive = false; + } +} \ No newline at end of file diff --git a/App/Frontend/src/app/app.routes.ts b/App/Frontend/src/app/app.routes.ts index 6e07d1b..2fcba58 100644 --- a/App/Frontend/src/app/app.routes.ts +++ b/App/Frontend/src/app/app.routes.ts @@ -7,6 +7,7 @@ import { Routes } from '@angular/router'; import { LoginComponent } from './login/login.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { SearchComponent } from './search/search.component'; +import { AddComponent } from "./add/add.component"; import {SpiderWebComponent} from "./spider-web/spider-web.component"; import { NotFoundComponent } from './notfound/notfound.component'; @@ -15,6 +16,7 @@ export const routes: Routes = [ { path: 'login', component: LoginComponent, data: { title: 'K10s - Login' } }, { path: 'dashboard', component: DashboardComponent, data: { title: 'K10s - Dashboard' } }, { path: 'search', component: SearchComponent, data: { title: 'K10s - Search' } }, + { path: 'add', component: AddComponent, data: { title: 'K10s - Add' } }, { path: 'spiderweb', component: SpiderWebComponent, data: { title: 'K10s - Testing' } }, { path: '**', component: NotFoundComponent, data: { title: 'K10s - Not Found' } } ]; \ No newline at end of file diff --git a/App/Frontend/src/app/byte-format.pipe.ts b/App/Frontend/src/app/byte-format.pipe.ts index 41b1ff3..4f480b6 100644 --- a/App/Frontend/src/app/byte-format.pipe.ts +++ b/App/Frontend/src/app/byte-format.pipe.ts @@ -1,22 +1,28 @@ +/**********************************/ +/* @since 01/01/2025 */ +/* @author K10s Open Source Team */ +/**********************************/ + import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ - standalone: true, - name: 'byteFormat' + standalone: true, + name: 'byteFormat' }) + export class ByteFormatPipe implements PipeTransform { - transform(value: number): string { - if (!value) { - return '0 GB'; - } + transform(value: number): string { + if (!value) { + return '0 GB'; + } - const GB = 1024 * 1024 * 1024; - const TB = GB * 1000; + const GB = 1024 * 1024 * 1024; + const TB = GB * 1000; - if (value >= TB) { - return (value / TB).toFixed(2) + ' TB'; - } else { - return (value / GB).toFixed(2) + ' GB'; + if (value >= TB) { + return (value / TB).toFixed(2) + ' TB'; + } else { + return (value / GB).toFixed(2) + ' GB'; + } } - } -} +} \ No newline at end of file diff --git a/App/Frontend/src/app/dashboard/dashboard.component.css b/App/Frontend/src/app/dashboard/dashboard.component.css index bda9a49..513a87d 100644 --- a/App/Frontend/src/app/dashboard/dashboard.component.css +++ b/App/Frontend/src/app/dashboard/dashboard.component.css @@ -44,7 +44,7 @@ border-radius: var(--radius); box-shadow: 2px 0 5px var(--shadow); width: 100%; - height: 200px; + height: 170px; font-weight: bold; font-size: 1rem; align-items: center; @@ -52,7 +52,6 @@ overflow: hidden; } - .dashboard-header-article { font-size: 1.2rem; font-weight: 700; @@ -67,16 +66,23 @@ margin: 10%; padding: 20%; } +#dashboard-header-2,#dashboard-header-3 { + float: left; + margin-left: -2.5rem; +} + .progress-container { width: 95%; height: 20px; - background-color: #E0E0E0; + background-color: rgba(212, 212, 212, 0); border-radius: 10px; overflow: hidden; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); - margin-left: 0.8rem; + margin-left: 0.5rem; +} +.progress-text { + margin-left: 1rem; } - .progress-bar { height: 100%; box-sizing: border-box; @@ -84,8 +90,7 @@ } .dashboard-header-article p { - margin: 0.8rem; - padding: 0; + padding-left: 4.5rem; !important; } /* Dashboard Header */ diff --git a/App/Frontend/src/app/dashboard/dashboard.component.html b/App/Frontend/src/app/dashboard/dashboard.component.html index a5c6ad1..462dfde 100644 --- a/App/Frontend/src/app/dashboard/dashboard.component.html +++ b/App/Frontend/src/app/dashboard/dashboard.component.html @@ -12,15 +12,14 @@

{{ 'DASHBOARD.NODES' | translate }}

{{ 'DASHBOARD.CPU' | translate }}

@@ -32,22 +31,20 @@

{{ 'DASHBOARD.CPU' | translate }}

{{ 'DASHBOARD.RAM' | translate }}

-

{{ 'DASHBOARD.DISK' | translate }}

@@ -55,7 +52,14 @@

{{ 'DASHBOARD.DISK' | translate }}

-

{{ usage.DiskUsage | byteFormat }} / {{ usage.DiskCapacity | byteFormat }}

+

+ + {{ usage.DiskUsage | byteFormat }} / {{ usage.DiskCapacity | byteFormat }} + +

+ + +
diff --git a/App/Frontend/src/app/dashboard/dashboard.component.ts b/App/Frontend/src/app/dashboard/dashboard.component.ts index a615afb..c968043 100644 --- a/App/Frontend/src/app/dashboard/dashboard.component.ts +++ b/App/Frontend/src/app/dashboard/dashboard.component.ts @@ -7,16 +7,17 @@ import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'; import { NavComponent } from '../nav/nav.component'; import { FooterComponent } from "../footer/footer.component"; import { TranslatePipe } from "@ngx-translate/core"; -import {StatsService} from "../services/stats.service"; -import {ByteFormatPipe} from "../byte-format.pipe"; -import {Color, NgxChartsModule, ScaleType} from "@swimlane/ngx-charts"; -import {SpiderWebComponent} from "../spider-web/spider-web.component"; +import { StatsService } from "../services/stats.service"; +import { ByteFormatPipe } from "../byte-format.pipe"; +import { Color, NgxChartsModule, ScaleType } from "@swimlane/ngx-charts"; +import { LoadingComponent } from "../loading/loading.component"; +import { SpiderWebComponent } from "../spider-web/spider-web.component"; @Component({ selector: 'app-dashboard', templateUrl: './dashboard.component.html', styleUrls: ['./dashboard.component.css'], - imports: [NavComponent, FooterComponent, TranslatePipe, ByteFormatPipe,NgxChartsModule,SpiderWebComponent], + imports: [NavComponent, FooterComponent, TranslatePipe, ByteFormatPipe, NgxChartsModule, SpiderWebComponent, LoadingComponent], standalone: true }) @@ -29,7 +30,6 @@ export class DashboardComponent implements AfterViewInit { @ViewChild('dashboardMain') dashboardMain!: ElementRef; @ViewChild('dashboardTitle') dashboardTitle!: ElementRef; - ngAfterViewInit(): void { const fullscreenButton = document.getElementById('dashboard-fullscreen-button'); if (fullscreenButton) fullscreenButton.addEventListener('click', () => this.toggleFullscreen()); @@ -61,7 +61,6 @@ export class DashboardComponent implements AfterViewInit { } } - // get stats usage: any = null; memoryChartData: any[] = []; @@ -90,19 +89,25 @@ export class DashboardComponent implements AfterViewInit { }); } + loading: boolean = false; loadUsage(): void { + this.loading = true; this.usageService.getStats().subscribe({ next: (data) => { - console.log(data) this.usage = data; this.updateChartData(); + this.loading = false; }, error: (error) => { console.error(error); + this.loading = false; } }); } + valueFormatting(usage: number): string { + return usage+`%`; + } updateChartData(): void { this.memoryChartData = [ @@ -130,9 +135,8 @@ export class DashboardComponent implements AfterViewInit { const orange = rootStyles.getPropertyValue('--status-orange').trim(); const red = rootStyles.getPropertyValue('--status-red').trim(); - if (usage < 50) return green; - if (usage < 80) return orange; + if (usage < 55) return green; + if (usage < 85) return orange; return red; } - } \ No newline at end of file diff --git a/App/Frontend/src/app/loading/loading.component.css b/App/Frontend/src/app/loading/loading.component.css new file mode 100644 index 0000000..f3e07e0 --- /dev/null +++ b/App/Frontend/src/app/loading/loading.component.css @@ -0,0 +1,20 @@ +/**********************************/ +/* @since 01/01/2025 */ +/* @author K10s Open Source Team */ +/**********************************/ + +/* Loading */ +.loader-icon { + border: 3px solid var(--secondary); + border-top: 3px solid transparent; + border-radius: 50%; + animation: spin 1s linear infinite; + display: block; + margin: 0 auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} +/* Loading */ \ No newline at end of file diff --git a/App/Frontend/src/app/loading/loading.component.html b/App/Frontend/src/app/loading/loading.component.html new file mode 100644 index 0000000..0b70a5a --- /dev/null +++ b/App/Frontend/src/app/loading/loading.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/App/Frontend/src/app/loading/loading.component.ts b/App/Frontend/src/app/loading/loading.component.ts new file mode 100644 index 0000000..b443368 --- /dev/null +++ b/App/Frontend/src/app/loading/loading.component.ts @@ -0,0 +1,17 @@ +/**********************************/ +/* @since 01/01/2025 */ +/* @author K10s Open Source Team */ +/**********************************/ + +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-loading', + templateUrl: './loading.component.html', + standalone: true, + styleUrls: ['./loading.component.css'] +}) + +export class LoadingComponent { + @Input() size: number = 24; +} diff --git a/App/Frontend/src/app/nav/nav.component.css b/App/Frontend/src/app/nav/nav.component.css index 8288fe2..955c72f 100644 --- a/App/Frontend/src/app/nav/nav.component.css +++ b/App/Frontend/src/app/nav/nav.component.css @@ -5,13 +5,13 @@ /* Navigation Bar */ ::ng-deep .nav { - background-color: var(--tertiary); - color: var(--secondary); - border-radius: var(--radius); - box-shadow: 2px 0 5px var(--shadow); - min-width: 150px; - padding: 20px; - top: 20px; + background-color: var(--tertiary); + color: var(--secondary); + border-radius: var(--radius); + box-shadow: 2px 0 5px var(--shadow); + min-width: 150px; + padding: 20px; + top: 20px; } .nav-header { @@ -251,5 +251,4 @@ background-color: var(--accent); color: var(--background); } - /* Modal */ \ No newline at end of file diff --git a/App/Frontend/src/app/nav/nav.component.ts b/App/Frontend/src/app/nav/nav.component.ts index 5887ecd..6d2e10c 100644 --- a/App/Frontend/src/app/nav/nav.component.ts +++ b/App/Frontend/src/app/nav/nav.component.ts @@ -85,4 +85,4 @@ export class NavComponent implements OnInit { htmlElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); } -} +} \ No newline at end of file diff --git a/App/Frontend/src/app/search/search.component.css b/App/Frontend/src/app/search/search.component.css index c2a3f97..0a73d9c 100644 --- a/App/Frontend/src/app/search/search.component.css +++ b/App/Frontend/src/app/search/search.component.css @@ -121,6 +121,19 @@ margin: 10px; } +.search-header-article2 input[type="radio"] { + appearance: none; + position: absolute; + width: 0; + height: 0; +} + +.search-header-article2 label:has(input[type="radio"]:checked) { + background-color: var(--primary); + color: var(--text); + border-color: var(--primary); +} + .search-dropdown1, .search-dropdown2 { position: relative; @@ -202,6 +215,8 @@ width: 100%; height: 100%; overflow: hidden; + display: flex; + flex-direction: column; } .search-table { @@ -266,7 +281,10 @@ } .search-main-pagination { - margin: 2px; + margin-top: auto; + padding: 10px; + display: flex; + justify-content: start; } .search-main-pagination button { @@ -286,6 +304,47 @@ } /* Search Main */ +/* Skeleton Table */ +.skeleton-table { + margin: 1rem; + display: grid; +} + +.skeleton-header { + width: 100%; + height: 2.8rem; + background-color: var(--secondary); + border-radius: 4px; +} + +.skeleton-content { + width: 100%; + height: 2.2rem; + border-radius: 4px; + animation: shimmer 1.2s infinite linear; +} + +.odd-row { + background-color: var(--quaternary); +} + +.even-row { + background-color: var(--tertiary); +} + +@keyframes shimmer { + 0% { + opacity: 0.2; + } + 50% { + opacity: 0.6; + } + 100% { + opacity: 0.2; + } +} +/* Skeleton Table */ + /* Media Queries */ @media screen and (max-width: 1360px) { .search-body { @@ -334,4 +393,5 @@ .search-table thead { display: none; } -} \ No newline at end of file +} +/* Media Queries */ \ No newline at end of file diff --git a/App/Frontend/src/app/search/search.component.html b/App/Frontend/src/app/search/search.component.html index bd4f340..984d5ba 100644 --- a/App/Frontend/src/app/search/search.component.html +++ b/App/Frontend/src/app/search/search.component.html @@ -8,16 +8,15 @@
-
- - - - - - -
-
- +
+ + + + + + +
+
@@ -48,204 +47,41 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'SEARCH.CLUSTER_NAME' | translate }}{{ 'SEARCH.NAMESPACE' | translate }}{{ 'SEARCH.POD_NAME' | translate }}{{ 'SEARCH.STATUS' | translate }}{{ 'SEARCH.RESTART_COUNT' | translate }}{{ 'SEARCH.LAST_UPDATED' | translate }}
Cluster-Adefaultnginx-pod-1Running02025-01-04 10:15:30
Cluster-Bkube-systemkube-proxy-2Running12025-01-03 18:45:12
Cluster-Cproductionbackend-api-4CrashLoopBackOff52025-01-04 09:30:45
Cluster-Dstagingfrontend-web-3Pending02025-01-03 22:12:20
Cluster-Emonitoringprometheus-pod-7Running22025-01-04 08:50:10
Cluster-Fdefaultredis-cache-9Running02025-01-04 07:45:00
Cluster-Gdevworker-node-5ImagePullBackOff32025-01-04 10:00:00
Cluster-Htestdb-service-10Running02025-01-04 06:30:30
Cluster-Idefaultnginx-pod-1Running02025-01-04 10:15:30
Cluster-Jkube-systemkube-proxy-2Running12025-01-03 18:45:12
Cluster-Lproductionbackend-api-4CrashLoopBackOff52025-01-04 09:30:45
Cluster-Mstagingfrontend-web-3Pending02025-01-03 22:12:20
Cluster-Omonitoringprometheus-pod-7Running22025-01-04 08:50:10
Cluster-Pdefaultredis-cache-9Running02025-01-04 07:45:00
Cluster-Kdevworker-node-5ImagePullBackOff32025-01-04 10:00:00
Cluster-Rtestdb-service-10Running02025-01-04 06:30:30
Cluster-Sdefaultnginx-pod-1Running02025-01-04 10:15:30
Cluster-Tdefaultredis-cache-9Running02025-01-04 07:45:00
Cluster-Vdevworker-node-5ImagePullBackOff32025-01-04 10:00:00
Cluster-Wtestdb-service-10Running02025-01-04 06:30:30
Cluster-Xdefaultnginx-pod-1Running02025-01-04 10:15:30
Cluster-Ydevworker-node-5ImagePullBackOff32025-01-04 10:00:00
Cluster-Ztestdb-service-10Running02025-01-04 06:30:30
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
{{ 'SEARCH.CLUSTER_NAME' | translate }}{{ 'SEARCH.NAMESPACE' | translate }}{{ 'SEARCH.POD_NAME' | translate }}{{ 'SEARCH.STATUS' | translate }}{{ 'SEARCH.RESTART_COUNT' | translate }}{{ 'SEARCH.LAST_UPDATED' | translate }}
{{ item.cluster }}{{ item.namespace }}{{ item.podName }}{{ item.status }}{{ item.restartCount }}{{ item.lastUpdated }}
+
diff --git a/App/Frontend/src/app/search/search.component.ts b/App/Frontend/src/app/search/search.component.ts index ea836c6..7890c45 100644 --- a/App/Frontend/src/app/search/search.component.ts +++ b/App/Frontend/src/app/search/search.component.ts @@ -8,6 +8,7 @@ import { NavComponent } from '../nav/nav.component'; import { FooterComponent } from "../footer/footer.component"; import { CommonModule } from "@angular/common"; import { TranslatePipe } from "@ngx-translate/core"; +import { LoadingComponent } from "../loading/loading.component"; @Component({ selector: 'app-search', @@ -18,6 +19,7 @@ import { TranslatePipe } from "@ngx-translate/core"; }) export class SearchComponent { + isLoading: boolean = true; dropdowns: { [key: string]: boolean } = { searchDropdown1: false, searchDropdown2: false, @@ -25,18 +27,30 @@ export class SearchComponent { }; selectedNode: string = 'None'; selectedNamespace: string = 'None'; + searchResults: any[] = []; toggleDropdown(dropdownKey: string) { for (let key in this.dropdowns) { this.dropdowns[key] = key === dropdownKey ? !this.dropdowns[key] : false; } } + selectNode(node: string) { this.selectedNode = node; this.toggleDropdown('searchDropdown1'); } + selectNamespace(namespace: string) { this.selectedNamespace = namespace; this.toggleDropdown('searchDropdown2'); } + + ngOnInit(): void { + this.getData(); + } + + getData(): void { + this.isLoading = true; + //todo get + } } \ No newline at end of file diff --git a/App/Frontend/src/app/services/add.service.ts b/App/Frontend/src/app/services/add.service.ts new file mode 100644 index 0000000..2f1cd73 --- /dev/null +++ b/App/Frontend/src/app/services/add.service.ts @@ -0,0 +1,28 @@ +/**********************************/ +/* @since 01/01/2025 */ +/* @author K10s Open Source Team */ +/**********************************/ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { catchError, Observable, throwError } from 'rxjs'; +import { BASE_URL } from "./stats.service"; + +@Injectable({ + providedIn: 'root' +}) + +export class AddService { + private uploadUrl = `${BASE_URL}/upload`; + + constructor(private http: HttpClient) {} + + uploadYaml(yaml: string): Observable { + return this.http.post(this.uploadUrl, { yaml }, { withCredentials: true }).pipe( + catchError(error => { + console.error('YAML upload failed', error); + return throwError(() => error); + }) + ); + } +} \ No newline at end of file diff --git a/App/Frontend/src/app/services/stats.service.ts b/App/Frontend/src/app/services/stats.service.ts index 795004a..eef4279 100644 --- a/App/Frontend/src/app/services/stats.service.ts +++ b/App/Frontend/src/app/services/stats.service.ts @@ -1,28 +1,34 @@ +/**********************************/ +/* @since 01/01/2025 */ +/* @author K10s Open Source Team */ +/**********************************/ + import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import {catchError, Observable, tap, throwError} from 'rxjs'; -const BASE_URL = 'http://localhost:8080'; +import { catchError, Observable, throwError } from 'rxjs'; +export const BASE_URL = 'http://localhost:8080'; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) + export class StatsService { - private apiUrl = `${BASE_URL}/secured/stats`; - private loginUrl = `${BASE_URL}/login`; + private apiUrl = `${BASE_URL}/secured/stats`; + private loginUrl = `${BASE_URL}/login`; - constructor(private http: HttpClient) {} + constructor(private http: HttpClient) {} - login(): Observable { - const loginData = { username: 'admin', password: 'password' }; + login(): Observable { + const loginData = { username: 'admin', password: 'password' }; - return this.http.post(this.loginUrl, loginData,{ withCredentials: true }).pipe( - catchError(error => { - return throwError(() => error); - }) - ); - } + return this.http.post(this.loginUrl, loginData,{ withCredentials: true }).pipe( + catchError(error => { + return throwError(() => error); + }) + ); + } - getStats(): Observable { - return this.http.get(this.apiUrl,{withCredentials: true}); - } -} + getStats(): Observable { + return this.http.get(this.apiUrl,{withCredentials: true}); + } +} \ No newline at end of file diff --git a/App/Frontend/src/assets/i18n/de.json b/App/Frontend/src/assets/i18n/de.json index 4d5cf5f..02195ec 100644 --- a/App/Frontend/src/assets/i18n/de.json +++ b/App/Frontend/src/assets/i18n/de.json @@ -44,5 +44,9 @@ }, "NOTFOUND": { "TITLE": "Es mag Open Source sein, aber bleib verdammt noch mal in deiner Spur ;-)" + }, + "ADD_DEPLOYMENT": { + "YAML_PLACEHOLDER": "Geben Sie die YAML hier ein...", + "DEPLOY" : "Bereitstellen" } } \ No newline at end of file diff --git a/App/Frontend/src/assets/i18n/en.json b/App/Frontend/src/assets/i18n/en.json index 622b293..9a1841b 100644 --- a/App/Frontend/src/assets/i18n/en.json +++ b/App/Frontend/src/assets/i18n/en.json @@ -44,5 +44,9 @@ }, "NOTFOUND": { "TITLE": "It may be open source, but stay in your damn lane ;-)" + }, + "ADD_DEPLOYMENT": { + "YAML_PLACEHOLDER": "Type YAML here...", + "DEPLOY" : "Deploy" } } \ No newline at end of file diff --git a/App/Frontend/src/assets/i18n/fr.json b/App/Frontend/src/assets/i18n/fr.json index 6977bec..35c68ac 100644 --- a/App/Frontend/src/assets/i18n/fr.json +++ b/App/Frontend/src/assets/i18n/fr.json @@ -44,5 +44,9 @@ }, "NOTFOUND": { "TITLE": "C’est peut-être open source, mais reste bien dans ton foutu couloir ;-)" + }, + "ADD_DEPLOYMENT": { + "YAML_PLACEHOLDER": "Saisir YAML ici...", + "DEPLOY" : "Déployer" } } \ No newline at end of file diff --git a/App/Frontend/src/assets/i18n/nl.json b/App/Frontend/src/assets/i18n/nl.json index e4b477a..9704ab0 100644 --- a/App/Frontend/src/assets/i18n/nl.json +++ b/App/Frontend/src/assets/i18n/nl.json @@ -44,5 +44,9 @@ }, "NOTFOUND": { "TITLE": "Het mag dan open source zijn, maar blijf verdomme in je eigen baan ;-)" + }, + "ADD_DEPLOYMENT": { + "YAML_PLACEHOLDER": "Typ hier de YAML...", + "DEPLOY" : "Installeer" } } \ No newline at end of file diff --git a/App/Frontend/src/assets/i18n/zh.json b/App/Frontend/src/assets/i18n/zh.json index 425137c..30b8a81 100644 --- a/App/Frontend/src/assets/i18n/zh.json +++ b/App/Frontend/src/assets/i18n/zh.json @@ -44,5 +44,9 @@ }, "NOTFOUND": { "TITLE": "也许是开源的,但别他妈越界 ;-)" + }, + "ADD_DEPLOYMENT": { + "YAML_PLACEHOLDER": "在此处键入 YAML...", + "DEPLOY" : "部署" } } \ No newline at end of file diff --git a/App/Frontend/src/main.ts b/App/Frontend/src/main.ts index 96ff1c7..cbf0e7f 100644 --- a/App/Frontend/src/main.ts +++ b/App/Frontend/src/main.ts @@ -6,9 +6,9 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; import { appConfig } from './app/app.config'; -import {TranslateHttpLoader} from "@ngx-translate/http-loader"; -import {HttpClient} from "@angular/common/http"; -import {TranslateLoader} from "@ngx-translate/core"; +import { TranslateHttpLoader } from "@ngx-translate/http-loader"; +import { HttpClient } from "@angular/common/http"; +import { TranslateLoader } from "@ngx-translate/core"; export function HttpLoaderFactory(http: HttpClient): TranslateLoader { return new TranslateHttpLoader(http, 'assets/i18n/', '.json'); diff --git a/App/Frontend/src/styles.css b/App/Frontend/src/styles.css index 7e26d09..232aff6 100644 --- a/App/Frontend/src/styles.css +++ b/App/Frontend/src/styles.css @@ -11,7 +11,7 @@ --secondary: #3c3154; --tertiary: #262b2e; --quaternary: #1f2326; - --accent: #5f4270; + --accent: #000000; } :root[data-theme="light"] {