diff --git a/.dockerignore b/.dockerignore index e2727550..dc0c6d99 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -**/node_modules_AAAA +**/node_modules Dockerfile .dockerignore .git @@ -9,4 +9,4 @@ docker-compose.* kind.* .eslintrc.js README.md -**/dist_AAAAA \ No newline at end of file +**/dist \ No newline at end of file diff --git a/.github/workflows/docker-prerelease.yaml b/.github/workflows/docker-prerelease.yaml index 4201e4fc..c357333d 100644 --- a/.github/workflows/docker-prerelease.yaml +++ b/.github/workflows/docker-prerelease.yaml @@ -74,7 +74,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push Docker image id: kubero-build-and-push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . push: true diff --git a/.github/workflows/jest-codecov.yaml b/.github/workflows/jest-codecov.yaml new file mode 100644 index 00000000..d19af1b5 --- /dev/null +++ b/.github/workflows/jest-codecov.yaml @@ -0,0 +1,20 @@ +name: 'Jest Codecov' +on: + workflow_dispatch: +defaults: + run: + working-directory: ./server +jobs: + codecov: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 # checkout the repo + - name: Install dependencies # install dependencies + run: yarn install --frozen-lockfile + - run: yarn build # install packages + - run: yarn test # run tests (configured to use jest-junit reporter) + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + directory: server/coverage + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/jest-pr.yaml b/.github/workflows/jest-pr.yaml new file mode 100644 index 00000000..d373388b --- /dev/null +++ b/.github/workflows/jest-pr.yaml @@ -0,0 +1,29 @@ +name: 'Jest PR Test' +on: + pull_request: + workflow_dispatch: +permissions: + pull-requests: write +defaults: + run: + working-directory: ./server +jobs: + build-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 # checkout the repo + - name: Install dependencies # install dependencies + run: yarn install --frozen-lockfile + - run: yarn build # install packages + - run: yarn test:ci # run tests (configured to use jest-junit reporter) + - name: Jest Coverage Comment + uses: MishaKav/jest-coverage-comment@main + with: + coverage-summary-path: server/coverage/coverage-summary.json + junitxml-path: server/reports/jest-junit.xml + - uses: actions/upload-artifact@v4 # upload test results + if: ${{ !cancelled() }} # run this step even if previous step failed + with: + name: test-results # Name of the check run which will be created + path: server/reports/jest-junit.xml + reporter: jest-junit # Format of test results \ No newline at end of file diff --git a/.github/workflows/jest-report.yaml b/.github/workflows/jest-report.yaml new file mode 100644 index 00000000..2fe61419 --- /dev/null +++ b/.github/workflows/jest-report.yaml @@ -0,0 +1,20 @@ +name: 'Jest Test Report' +on: + workflow_run: + workflows: ['Jest PR Test'] # runs after 'Jest PR Test' workflow + types: + - completed +permissions: + contents: read + actions: read + checks: write +jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v2 + with: + artifact: test-results # artifact name + name: JEST Tests # Name of the check run which will be created + path: '*.xml' # Path to test results (inside artifact .zip) + reporter: jest-junit # Format of test results \ No newline at end of file diff --git a/.github/workflows/stargazers.yaml b/.github/workflows/stargazers.yaml index d7662237..67858c98 100644 --- a/.github/workflows/stargazers.yaml +++ b/.github/workflows/stargazers.yaml @@ -8,14 +8,21 @@ jobs: name: 'stargazers' runs-on: ubuntu-latest steps: - - name: 💌 Send email, you star + - name: Send email uses: dawidd6/action-send-mail@v1.3.0 with: server_address: smtp.gmail.com server_port: 465 username: ${{ secrets.GMAIL_USER }} password: ${{ secrets.GMAIL_PASS }} - subject: Your a star ✨ - body: ${{ github.actor }} just starred your mail-on-star repo!!! ${{ github.repository }} + subject: ${{ github.event.repository.stargazers_count }} ✨ ${{ github.actor }} stared ${{ github.repository }} + body: | + ${{ github.actor }} just starred your mail-on-star repo!!! + ${{ github.event.repository.html_url }} + Forks: ${{ github.event.repository.forks_count }} + Stars: ${{ github.event.repository.stargazers_count }} + Issues: ${{ github.event.repository.open_issues_count }} + + ${{ github.repository }} to: ${{ secrets.GMAIL_ADDRESS }} from: ${{ secrets.GMAIL_ADDRESS }} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8db5f7b8..9a9e13ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,17 +13,22 @@ Willing to contribute something, but you don't know where to start? Have a look 5. Open a PR ## Techstack -**Infrastructure** -- [Kubernetes](https://kubernetes.io/) -- [Operator SDK](https://sdk.operatorframework.io/) -- [Helm (Operator)](https://helm.sh/) -- [Kind (Development)](https://kind.sigs.k8s.io/) - -**Code** -- [Express](https://expressjs.com/) -- [TypeScript](https://www.typescriptlang.org/) -- [Vue.js](https://vuejs.org/) -- [Vuetify](https://vuetifyjs.com/en/) +- Backend + - [NestJS](https://nestjs.com/) + - [TypeScript](https://www.typescriptlang.org/) + - [Jest](https://jestjs.io/) +- Frontend + - [Vue.js](https://vuejs.org/) + - [Vuetify](https://vuetifyjs.com/en/) +- CLI + - [Go](https://golang.org/) + - [Cobra](https://cobra.dev/) +- Operator + - [Operator SDK](https://sdk.operatorframework.io/) + - [Helm](https://helm.sh/) +- Infrastructure + - [Kubernetes](https://kubernetes.io/) + - [Kind (Development)](https://kind.sigs.k8s.io/) ## Development setup for the Kubero UI diff --git a/Dockerfile b/Dockerfile index 0ba5f3dc..fcf64753 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,7 @@ COPY server ./server RUN cd /build/server && \ yarn install RUN cd /build/server && \ - yarn build && \ - yarn swaggergen + yarn build ## Client COPY client ./client @@ -18,7 +17,7 @@ RUN cd /build/client && \ RUN cd /build/client && \ yarn build -FROM build AS release +FROM node:22-alpine AS release ARG VERSION=unknown LABEL maintainer='www.kubero.dev' @@ -30,13 +29,15 @@ WORKDIR /app/ COPY --from=build /build/server/dist /app/server COPY --from=build /build/server/package.json /app/server/package.json -COPY --from=build /build/server/src/modules/templates /app/server/modules/templates +COPY --from=build /build/server/src/deployments/templates /app/server/deployments/templates COPY --from=build /build/server/node_modules /app/server/node_modules -COPY --from=build /build/server/swagger.json /app/swagger.json + +# temporary fix for the public folder +COPY --from=build /build/server/dist/public /app/server/public RUN echo -n $VERSION > /app/server/VERSION WORKDIR /app/server -CMD [ "node", "index.js" ] \ No newline at end of file +CMD [ "node", "main" ] \ No newline at end of file diff --git a/README.md b/README.md index 9a0fedbc..e34e3424 100644 --- a/README.md +++ b/README.md @@ -4,40 +4,36 @@ [![License](https://img.shields.io/github/license/kubero-dev/kubero?style=flat-square&color=blue")](https://github.com/kubero-dev/kubero/blob/main/LICENSE) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/kubero-dev/kubero?style=flat-square&color=brightgreen)](https://github.com/kubero-dev/kubero/releases/latest) +[![codecov](https://codecov.io/github/kubero-dev/kubero/branch/main-refactored/graph/badge.svg?token=3J3CWUXG5Z&style=flat-square)](https://codecov.io/github/kubero-dev/kubero) [![Discord](https://img.shields.io/discord/1051249947472826408?style=flat-square)](https://discord.gg/tafRPMWS4r) [![GitHub (Pre-)Release Date](https://img.shields.io/github/release-date-pre/kubero-dev/kubero?style=flat-square)](https://github.com/kubero-dev/kubero/releases/latest) [![Demo](https://img.shields.io/badge/demo-up-sucess?style=flat-square&color=blue)](https://demo.kubero.dev) Kubero [pronounced: Kube Hero] is a self-hosted PaaS (Platform as a Service) that allows any developer to deploy their application on Kubernetes without specialized knowledge. Kubero follows the principles of 12-factor apps. It is possible to run apps based on existing containers or from source code. -> [!NOTE] -> Kubero v3.0.0 is on the way! -> -> We're gearing up for the Kubero v3.0.0 release! 🎉 This major update features a rewritten backend, now powered by NestJS, bringing best practices and maintainability. - ![](https://raw.githubusercontent.com/kubero-dev/docs/refs/heads/main/static/assets/screenshots/createapp.gif) More [Screenshots](https://www.kubero.dev/docs/screenshots) and a full video on [YouTube](https://www.youtube.com/watch?v=kmqhddc6UlI) ## Features ([DEMO](https://demo.kubero.dev)) -- **CI/CD Pipelines:** Create unlimited pipelines with up to 4 separate staging environments for all your applications. -- **GitOps Review Apps:** Automatically build, start, and clean up review apps when opening or closing pull requests. -- **Automatic Redeployments:** Trigger app redeployments on pushes to branches or tags. -- **Docker Deployments:** Deploy Docker containers on Kubernetes without needing Helm charts. -- **App Templates:** Deploy popular applications like WordPress and Grafana with ready-to-use templates. -- **Add-ons Integration:** Seamlessly deploy add-ons such as PostgreSQL and Redis alongside your applications. -- **API & CLI:** Integrate seamlessly with existing tools and CI/CD workflows. -- **Metrics & Monitoring:** Access integrated metrics to monitor application health. -- **Notifications:** Get build and deployment updates via Discord, Slack, or Webhooks. -- **Vulnerability Scans:** Perform scheduled or triggered scans for running applications. -- **Application Logs:** View logs directly from the web UI for easy monitoring. -- **Safe Restarts:** Restart applications safely and easily through the web UI. -- **Web Console:** Use the built-in container web console for direct access. -- **Scheduled Tasks:** Easily create and manage cronjobs. -- **Multi-Tenancy:** Support for managing multiple tenants. -- **Single Sign-On (SSO):** Authenticate securely with GitHub and OAuth2. -- **Basic Auth:** Configure Basic Auth for your applications with ease. +- **CI/CD Pipelines**
Create unlimited pipelines with up to 4 separate staging environments for all your applications. +- **GitOps Review Apps**
Automatically build, start, and clean up review apps when opening or closing pull requests. +- **Automatic Redeployments**
Trigger app redeployments on pushes to branches or tags. +- **Docker Deployments**
Deploy Docker containers on Kubernetes without needing Helm charts. +- **App Templates**
Deploy popular applications like WordPress and Grafana with ready-to-use templates. +- **Add-ons Integration**
Seamlessly deploy add-ons such as PostgreSQL and Redis alongside your applications. +- **API & CLI**
Integrate seamlessly with existing tools and CI/CD workflows. +- **Metrics & Monitoring**
Access integrated metrics to monitor application health. +- **Notifications**
Get build and deployment updates via Discord, Slack, or Webhooks. +- **Vulnerability Scans**
Perform scheduled or triggered scans for running applications. +- **Application Logs**
View logs directly from the web UI for easy monitoring. +- **Safe Restarts**
Restart applications safely and easily through the web UI. +- **Web Console**
Use the built-in container web console for direct access. +- **Scheduled Tasks**
Easily create and manage cronjobs. +- **Multi-Tenancy**
Support for managing multiple tenants. +- **Single Sign-On (SSO)**
Authenticate securely with GitHub and OAuth2. +- **Basic Auth**
Configure Basic Auth for your applications with ease. ## Basic Concept @@ -134,11 +130,28 @@ Basically *everything* that can be packaged in a single container can be deploye Kubero starts now building your app. Once the build is complete, Kubero will launch the final container and make it accessible via the configured domain. -## Documentation -https://www.kubero.dev/docs/quickstart - -## Roadmap -https://github.com/orgs/kubero-dev/projects/1/views/3 +## Techstack + +- Backend + - [NestJS](https://nestjs.com/) + - [TypeScript](https://www.typescriptlang.org/) + - [Jest](https://jestjs.io/) +- Frontend + - [Vue.js](https://vuejs.org/) + - [Vuetify](https://vuetifyjs.com/en/) +- CLI + - [Go](https://golang.org/) + - [Cobra](https://cobra.dev/) +- Operator + - [Operator SDK](https://sdk.operatorframework.io/) + - [Helm](https://helm.sh/) +- Infrastructure + - [Kubernetes](https://kubernetes.io/) + - [Kind (Development)](https://kind.sigs.k8s.io/) + +## Links +- Documentation https://www.kubero.dev/docs/ +- Roadmap https://github.com/orgs/kubero-dev/projects/1/views/3 ## Community [![kubero Discord server Banner](https://discordapp.com/api/guilds/1051249947472826408/widget.png?style=banner2)](https://discord.gg/tafRPMWS4r) diff --git a/SECURITY.md b/SECURITY.md index e7b33dd4..66751aaa 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,6 +5,7 @@ | Version | Supported | | ------- | ------------------ | | 2.X.X | :white_check_mark: | +| 2.X.X | EOL 31.03.2025 | | 1.X.X | EOL 28.09.2024 | | 0.X.X | | diff --git a/client/package.json b/client/package.json index 5ef8a6b4..582082f8 100644 --- a/client/package.json +++ b/client/package.json @@ -3,8 +3,8 @@ "private": true, "license": "GPL-3.0", "scripts": { - "dev": "vite", - "watch": "vue-tsc --noEmit && vite build --watch", + "run": "vite", + "dev": "vue-tsc --noEmit && vite build --watch", "build": "vue-tsc --noEmit && vite build", "preview": "vite preview", "lint": "eslint . --fix --ignore-path .gitignore" @@ -44,8 +44,8 @@ "sass": "^1.60.0", "typescript": "^5.0.0", "unplugin-fonts": "^1.0.3", - "vite": "^5.1.8", - "vite-plugin-vuetify": "^1.0.0", - "vue-tsc": "^2.1.0" + "vite": "^6.3.4", + "vite-plugin-vuetify": "^2.1.1", + "vue-tsc": "^2.2.10" } } diff --git a/client/src/components/apps/addons.vue b/client/src/components/apps/addons.vue index 19e65868..bdc66d2f 100644 --- a/client/src/components/apps/addons.vue +++ b/client/src/components/apps/addons.vue @@ -281,7 +281,7 @@ export default defineComponent({ this.dialog = true; }, loadStorageClasses() { - axios.get(`/api/config/storageclasses`) + axios.get(`/api/kubernetes/storageclasses`) .then(response => { for (let storageClass of response.data) { this.availableStorageClasses.push({ diff --git a/client/src/components/apps/alerts.vue b/client/src/components/apps/alerts.vue index 3e08132e..82494f73 100644 --- a/client/src/components/apps/alerts.vue +++ b/client/src/components/apps/alerts.vue @@ -84,7 +84,7 @@ export default { }, 10000); }, loadRules() { - axios.get(`/api/rules/${this.pipeline}/${this.phase}/${this.app}`) + axios.get(`/api/metrics/rules/${this.pipeline}/${this.phase}/${this.app}`) .then(response => { this.rules = response.data }) diff --git a/client/src/components/apps/appstats.vue b/client/src/components/apps/appstats.vue index e8d2bfea..3def6891 100644 --- a/client/src/components/apps/appstats.vue +++ b/client/src/components/apps/appstats.vue @@ -480,7 +480,7 @@ export default defineComponent({ }, methods: { loadUptimes() { - axios.get(`/api/uptimes/${this.pipeline}/${this.phase}`) + axios.get(`/api/metrics/uptimes/${this.pipeline}/${this.phase}`) .then(response => { this.uptimes = response.data; this.loadMetrics(); @@ -491,7 +491,7 @@ export default defineComponent({ }, loadMetrics() { - axios.get(`/api/metrics/${this.pipeline}/${this.phase}/${this.app}`) + axios.get(`/api/metrics/resources/${this.pipeline}/${this.phase}/${this.app}`) .then(response => { for (var i = 0; i < response.data.length; i++) { if (response.data[i].cpu.percentage != null && response.data[i].memory.percentage != null) { diff --git a/client/src/components/apps/buildsform.vue b/client/src/components/apps/buildsform.vue index 380741dc..018a8fff 100644 --- a/client/src/components/apps/buildsform.vue +++ b/client/src/components/apps/buildsform.vue @@ -137,7 +137,7 @@ export default defineComponent({ const repoB64 = btoa(this.appData?.spec.gitrepo.ssh_url) //const provider = this.appData?.spec.gitrepo.provider const provider = "github" // TODO: FIX: get provider from appData - axios.get(`/api/repo/${provider}/${repoB64}/references/list`) + axios.get(`/api/repo/${provider}/${repoB64}/references`) .then(response => { this.references = response.data }) diff --git a/client/src/components/apps/console.vue b/client/src/components/apps/console.vue index 37c7981e..6d5e5808 100644 --- a/client/src/components/apps/console.vue +++ b/client/src/components/apps/console.vue @@ -109,7 +109,7 @@ export default defineComponent({ }), methods: { loadPods() { - axios.get(`/api/status/pods/${this.pipeline}/${this.phase}/${this.app}`).then((response) => { + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/pods`).then((response) => { //this.loadContainers(); for (let pod of response.data) { const p = {name: pod.name, containers: pod.containers.map((c: any) => c.name)} as Pod; @@ -258,7 +258,7 @@ export default defineComponent({ }); }, execInContainer(){ - axios.post(`/api/console/${this.pipeline}/${this.phase}/${this.app}/exec`, { + axios.post(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/console`, { podName: this.pod.name, containerName: this.container, command: this.command, diff --git a/client/src/components/apps/detail.vue b/client/src/components/apps/detail.vue index 2df38c49..d05a6a2f 100644 --- a/client/src/components/apps/detail.vue +++ b/client/src/components/apps/detail.vue @@ -160,7 +160,7 @@ export default defineComponent({ }); }, loadApp() { - axios.get('/api/pipelines/'+this.pipeline+'/'+this.phase+'/'+this.app).then(response => { + axios.get('/api/apps/'+this.pipeline+'/'+this.phase+'/'+this.app).then(response => { this.appData = response.data; //console.log(this.appData); }); @@ -172,7 +172,7 @@ export default defineComponent({ this.$router.push(`/pipeline/${this.pipeline}/${this.phase}/apps/${this.app}`); }, ActionStartDownload() { - axios.get('/api/pipelines/'+this.pipeline+'/'+this.phase+'/'+this.app+'/download').then(response => { + axios.get('/api/apps/'+this.pipeline+'/'+this.phase+'/'+this.app+'/download').then(response => { //console.log(response.data); const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); @@ -197,7 +197,7 @@ export default defineComponent({ }) .then((result) => { if (result.isConfirmed) { - axios.delete(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`) + axios.delete(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`) .then(response => { // sleep 1 second setTimeout(() => { @@ -213,7 +213,7 @@ export default defineComponent({ }); }, async restartApp() { - axios.get(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}/restart`) + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/restart`) .then(response => { //console.log(response); this.loadingState = true; diff --git a/client/src/components/apps/events.vue b/client/src/components/apps/events.vue index 67998405..9d51b6dd 100644 --- a/client/src/components/apps/events.vue +++ b/client/src/components/apps/events.vue @@ -139,7 +139,7 @@ export default defineComponent({ const namespace = this.pipeline + "-" + this.phase; //axios.get(`/api/events?namespace=${this.$route.query.namespace}`) //console.log("loadEvents", namespace); - axios.get(`/api/events?namespace=${namespace}`) + axios.get(`/api/kubernetes/events?namespace=${namespace}`) .then(response => { // sort by creationTimestamp response.data.sort((a: any, b: any) => { diff --git a/client/src/components/apps/form.vue b/client/src/components/apps/form.vue index 153fd4df..db796f60 100644 --- a/client/src/components/apps/form.vue +++ b/client/src/components/apps/form.vue @@ -51,12 +51,12 @@ md="6" > @@ -64,6 +64,7 @@ cols="12" md="2" > + @@ -566,7 +568,7 @@ md="6" > @@ -881,7 +883,7 @@ Environment Variables - +
Addons
- + @@ -1307,6 +1309,7 @@ :disabled="!valid" >Update
+
@@ -1490,7 +1494,7 @@ export default defineComponent({ return { breadcrumbItems: [ { - title: 'dashboard.-', + title: 'dashboard.pipelines', disabled: false, to: { name: 'Pipelines', params: {}} }, @@ -1609,7 +1613,7 @@ export default defineComponent({ deploymentstrategy: 'docker', phases: [] as Phase[], }, - appname: '', + name: '', resourceVersion: '', /* phases: [ @@ -1643,7 +1647,7 @@ export default defineComponent({ }, autodeploy: true, sslIndex: [] as (boolean|undefined)[], - envvars: [ + envVars: [ //{ name: '', value: '' }, ] as EnvVar[], sAAnnotations: [ @@ -1711,7 +1715,6 @@ export default defineComponent({ letsecryptClusterIssuer: 'letsencrypt-prod', // deprecated in version 1.11.0 security: { - vulnerabilityScans: false, allowPrivilegeEscalation: false, runAsNonRoot: false, readOnlyRootFilesystem: true, @@ -1789,6 +1792,14 @@ export default defineComponent({ 'SYSLOG', 'WAKE_ALARM', ], + vulnerabilityscan: { + enabled: false, + schedule: "0 0 * * *", + image: { + repository: 'aquasec/trivy', + tag: 'latest', + }, + }, healthcheck: { enabled: true, path: '/', @@ -1877,7 +1888,7 @@ export default defineComponent({ return domainsList; }, getDomains() { - axios.get('/api/domains').then(response => { + axios.get('/api/kubernetes/domains').then(response => { this.takenDomains = this.whiteListDomains(response.data); }); }, @@ -1896,21 +1907,21 @@ export default defineComponent({ } }, loadClusterIssuers(){ - axios.get('/api/config/clusterissuers').then(response => { - this.letsecryptClusterIssuer = response.data.id; + axios.get('/api/config/clusterissuer').then(response => { + this.letsecryptClusterIssuer = response.data.clusterissuer; }); }, loadTemplate(template: string) { axios.get('/api/templates/'+template).then(response => { - this.appname = response.data.name; + this.name = response.data.name; this.containerPort = response.data.image.containerPort; this.deploymentstrategy = response.data.deploymentstrategy; this.docker.image = response.data.image.repository; this.docker.tag = response.data.image.tag; - this.envvars = response.data.envVars; + this.envVars = response.data.envVars; if (response.data.serviceAccount && response.data.serviceAccount.annotations) { this.sAAnnotations = Object.entries(response.data.serviceAccount.annotations).map(([key, value]) => ({annotation: key, value: value as string})); } @@ -1932,7 +1943,7 @@ export default defineComponent({ } // Open Panel if there is some data to show - if (this.envvars.length > 0) { + if (this.envVars.length > 0) { this.panel.push(6) } if (Object.keys(this.sAAnnotations).length > 0) { @@ -1989,12 +2000,12 @@ export default defineComponent({ // extract defaultEnvvars from pipeline phase for (let i = 0; i < this.pipelineData.phases.length; i++) { if (this.pipelineData.phases[i].name == this.phase) { - this.envvars = this.pipelineData.phases[i].defaultEnvvars; + this.envVars = this.pipelineData.phases[i].defaultEnvvars; } } // Open Panel if there is some data to show - if (this.envvars.length > 0) { + if (this.envVars.length > 0) { this.panel.push(6) } @@ -2032,7 +2043,7 @@ export default defineComponent({ }); }, loadStorageClasses() { - axios.get('/api/config/storageclasses').then(response => { + axios.get('/api/kubernetes/storageclasses').then(response => { for (let i = 0; i < response.data.length; i++) { this.storageclasses.push(response.data[i].name); } @@ -2049,7 +2060,7 @@ export default defineComponent({ const gitrepoB64 = btoa(this.pipelineData.git.repository.ssh_url); const gitprovider = this.pipelineData.git.provider; - axios.get('/api/repo/'+gitprovider+"/"+gitrepoB64+"/branches/list").then(response => { + axios.get('/api/repo/'+gitprovider+"/"+gitrepoB64+"/branches").then(response => { if (response.data.length === 0) { return; } @@ -2070,7 +2081,7 @@ export default defineComponent({ loadPodsizeList() { - axios.get('/api/config/podsize').then(response => { + axios.get('/api/config/podsizes').then(response => { if (response.data.length > 0 && this.app == 'new') { this.podsize = response.data[0]; } @@ -2089,7 +2100,7 @@ export default defineComponent({ }, loadBuildpacks() { - axios.get('/api/config/buildpacks').then(response => { + axios.get('/api/config/runpacks').then(response => { for (let i = 0; i < response.data.length; i++) { this.buildpacks.push({ text: response.data[i].name, @@ -2104,7 +2115,7 @@ export default defineComponent({ }, deleteApp() { - axios.delete(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`) + axios.delete(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`) .then(() => { // wait for 1 second and redirect to apps page // this avoids a race condition with the backend @@ -2118,8 +2129,8 @@ export default defineComponent({ }, loadApp() { if (this.app !== 'new') { - axios.get(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`).then(response => { - this.resourceVersion = response.data.resourceVersion; + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`).then(response => { + this.resourceVersion = response.data.metadata.resourceVersion; // Open Panel if there is some data to show if (response.data.spec.envVars.length > 0) { @@ -2144,7 +2155,7 @@ export default defineComponent({ this.deploymentstrategy = response.data.spec.deploymentstrategy; this.buildstrategy = response.data.spec.buildstrategy || 'plain'; - this.appname = response.data.spec.name; + this.name = response.data.spec.name; this.sleep = response.data.spec.sleep; this.basicAuth = response.data.spec.basicAuth || { enabled: false, realm: 'Authentication required', accounts: [] }; this.buildpack = { @@ -2159,7 +2170,7 @@ export default defineComponent({ this.docker.tag = response.data.spec.image.tag || 'latest'; this.docker.command = command; this.autodeploy = response.data.spec.autodeploy; - this.envvars = response.data.spec.envVars; + this.envVars = response.data.spec.envVars; this.serviceAccount = response.data.spec.serviceAccount; if (response.data.spec.serviceAccount && response.data.spec.serviceAccount.annotations) { this.sAAnnotations = Object.entries(response.data.spec.serviceAccount.annotations).map(([key, value]) => ({annotation: key, value: value as string})); @@ -2174,7 +2185,7 @@ export default defineComponent({ this.workerreplicasrange = [response.data.spec.worker.autoscaling.minReplicas, response.data.spec.worker.autoscaling.maxReplicas]; this.cronjobs = this.cronjobUnformat(response.data.spec.cronjobs) || []; this.addons= response.data.spec.addons || []; - this.security.vulnerabilityScans = response.data.spec.vulnerabilityscan.enabled; + this.vulnerabilityscan = response.data.spec.vulnerabilityscan; this.ingress = response.data.spec.ingress || {}; this.healthcheck = response.data.spec.healthcheck || { enabled: true, path: '/', startupSeconds: 90, timeoutSeconds: 30, periodSeconds: 10 }; @@ -2195,10 +2206,10 @@ export default defineComponent({ }, setSSL() { if (this.ingress.tls?.length == 0) { - this.ingress.tls = [{ hosts: [], secretName: this.appname+'-tls' }]; + this.ingress.tls = [{ hosts: [], secretName: this.name+'-tls' }]; } this.ingress.tls[0].hosts = []; - this.ingress.tls[0].secretName = this.appname+'-tls'; + this.ingress.tls[0].secretName = this.name+'-tls'; this.ingress.hosts.forEach((host, index) => { if (this.sslIndex[index]) { this.ingress.tls[0].hosts.push(host.host); @@ -2266,9 +2277,11 @@ export default defineComponent({ } let postdata = { + pipeline: this.pipeline, + phase: this.phase, resourceVersion: this.resourceVersion, buildpack: this.buildpack, - appname: this.appname, + name: this.name, sleep: this.sleep, basicAuth: this.basicAuth, gitrepo: this.gitrepo, @@ -2276,7 +2289,7 @@ export default defineComponent({ deploymentstrategy: this.deploymentstrategy, buildstrategy: this.buildstrategy, image : { - containerport: this.containerPort, + containerPort: this.containerPort, repository: this.docker.image, tag: this.docker.tag, command: command, @@ -2285,7 +2298,7 @@ export default defineComponent({ run: this.buildpack?.run, }, autodeploy: this.autodeploy, - envvars: this.envvars, + envVars: this.envVars, // loop through serviceaccount annotations and convert to object serviceAccount: { annotations: this.sAAnnotations.reduce((acc, cur) => { @@ -2318,6 +2331,7 @@ export default defineComponent({ addons: this.addons, security: this.security, ingress: this.ingress, + vulnerabilityscan: this.vulnerabilityscan, healthcheck: this.healthcheck, } @@ -2328,7 +2342,7 @@ export default defineComponent({ postdata.image.run.securityContext.runAsGroup = parseInt(postdata.image.run.securityContext.runAsGroup); } - axios.put(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`, postdata + axios.put(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/${this.resourceVersion}`, postdata // eslint-disable-next-line no-unused-vars ).then(response => { this.$router.push(`/pipeline/${this.pipeline}/apps`); @@ -2369,7 +2383,7 @@ export default defineComponent({ pipeline: this.pipeline, buildpack: this.buildpack, phase: this.phase, - appname: this.appname.toLowerCase(), + name: this.name.toLowerCase(), sleep: this.sleep, basicAuth: this.basicAuth, gitrepo: this.gitrepo, @@ -2377,7 +2391,7 @@ export default defineComponent({ deploymentstrategy: this.deploymentstrategy, buildstrategy: this.buildstrategy, image : { - containerport: this.containerPort, + containerPort: this.containerPort, repository: this.docker.image, tag: this.docker.tag, fetch: this.buildpack?.fetch, @@ -2385,7 +2399,7 @@ export default defineComponent({ run: this.buildpack?.run, }, autodeploy: this.autodeploy, - envvars: this.envvars, + envVars: this.envVars, serviceAccount: { annotations: this.sAAnnotations.reduce((acc, cur) => { acc[cur.annotation] = cur.value; @@ -2417,6 +2431,7 @@ export default defineComponent({ addons: this.addons, security: this.security, ingress: this.ingress, + vulnerabilityscan: this.vulnerabilityscan, healthcheck: this.healthcheck, } @@ -2442,10 +2457,10 @@ export default defineComponent({ }, } */ - axios.post(`/api/apps`, postdata) + axios.post(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`, postdata) // eslint-disable-next-line no-unused-vars .then(response => { - this.appname = ''; + this.name = ''; //console.log(response); this.$router.push({path: '/pipeline/' + this.pipeline + '/apps'}); }) @@ -2467,15 +2482,15 @@ export default defineComponent({ } }, addEnvLine() { - this.envvars.push({ + this.envVars.push({ name: '', value: '', }); }, removeEnvLine(index: string) { - for (let i = 0; i < this.envvars.length; i++) { - if (this.envvars[i].name === index) { - this.envvars.splice(i, 1); + for (let i = 0; i < this.envVars.length; i++) { + if (this.envVars[i].name === index) { + this.envVars.splice(i, 1); } } }, @@ -2512,8 +2527,8 @@ export default defineComponent({ const [name, value] = line.split('='); // check if name isn't commented out if (name && !name.startsWith('#') && value) { - if (!this.envvars.some(envvar => envvar.name === name.trim())) { - this.envvars.push({ name: name.trim(), value: value.trim() }); + if (!this.envVars.some(envVars => envVars.name === name.trim())) { + this.envVars.push({ name: name.trim(), value: value.trim() }); } } } diff --git a/client/src/components/apps/metrics.vue b/client/src/components/apps/metrics.vue index 53c1f814..39f7ac59 100644 --- a/client/src/components/apps/metrics.vue +++ b/client/src/components/apps/metrics.vue @@ -655,7 +655,7 @@ export default defineComponent({ }, getMemoryMetrics() { - axios.get(`/api/longtermmetrics/memory/${this.pipeline}/${this.phase}/${this.app}`, { + axios.get(`/api/metrics/timeseries/memory/${this.pipeline}/${this.phase}/${this.app}`, { params: { scale: this.scale } @@ -668,7 +668,7 @@ export default defineComponent({ }); }, getLoadMetrics() { - axios.get(`/api/longtermmetrics/load/${this.pipeline}/${this.phase}/${this.app}`, { + axios.get(`/api/metrics/timeseries/load/${this.pipeline}/${this.phase}/${this.app}`, { params: { scale: this.scale } @@ -681,9 +681,11 @@ export default defineComponent({ }); }, getHttpStatusCodeMetrics() { - axios.get(`/api/longtermmetrics/httpstatuscodes/${this.pipeline}/${this.phase}/${this.host}/rate`, { + axios.get(`/api/metrics/timeseries/httpstatuscodes/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -694,9 +696,11 @@ export default defineComponent({ }); }, getHttpStatusCodeIncreaseMetrics() { - axios.get(`/api/longtermmetrics/httpstatuscodes/${this.pipeline}/${this.phase}/${this.host}/increase`, { + axios.get(`/api/metrics/timeseries/httpstatuscodes/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -707,9 +711,11 @@ export default defineComponent({ }); }, getResponseTimeMetrics() { - axios.get(`/api/longtermmetrics/responsetime/${this.pipeline}/${this.phase}/${this.host}/increase`, { + axios.get(`/api/metrics/timeseries/responsetime/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -720,9 +726,11 @@ export default defineComponent({ }); }, getResponseTrafficMetrics() { - axios.get(`/api/longtermmetrics/traffic/${this.pipeline}/${this.phase}/${this.host}/increase`, { + axios.get(`/api/metrics/timeseries/traffic/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -734,9 +742,10 @@ export default defineComponent({ }, getCpuMetrics() { // use 'rate' instead of 'increase' when comparing to limit and request - axios.get(`/api/longtermmetrics/cpu/${this.pipeline}/${this.phase}/${this.app}/increase`, { + axios.get(`/api/metrics/timeseries/cpu/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + calc: 'rate' } }) .then((response) => { @@ -748,9 +757,10 @@ export default defineComponent({ }, getCpuMetricsRate() { // use 'rate' instead of 'increase' when comparing to limit and request - axios.get(`/api/longtermmetrics/cpu/${this.pipeline}/${this.phase}/${this.app}/rate`, { + axios.get(`/api/metrics/timeseries/cpu/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + calc: 'rate' } }) .then((response) => { diff --git a/client/src/components/apps/vulnerabilities.vue b/client/src/components/apps/vulnerabilities.vue index 12a53f63..132da44f 100644 --- a/client/src/components/apps/vulnerabilities.vue +++ b/client/src/components/apps/vulnerabilities.vue @@ -81,7 +81,7 @@ Exposed Ports Wrong username or password!
@@ -86,10 +87,14 @@ import router from "../router" import axios from "axios" import { defineComponent } from 'vue' +import { useCookies } from "vue3-cookies"; +const { cookies } = useCookies(); + export default defineComponent({ name: "Login", data: () => ({ error: false, + errorshake: false, authMethods : { "local": false, "github": false, @@ -120,18 +125,57 @@ export default defineComponent({ username: username, password: password } - axios.post("/api/login", data) + axios.post("/api/auth/login", data) .then((response) => { //console.log("Logged in"+response) - router.push("/") + + // Save topen token in local storage + //localStorage.setItem("kubero.JWT_TOKEN", response.data.access_token); + + const token = cookies.set("kubero.JWT_TOKEN", response.data.access_token); + window.location.href = "/" }) .catch((errors) => { this.error = true; + this.errorshake = true; + setTimeout(() => { + this.errorshake = false; + }, 300); console.log("Cannot log in"+errors) }) } login() + }, + github() { + axios.get("/api/auth/github") + .then((response) => { + //console.log("Logged in"+response) + + // Save topen token in local storage + //localStorage.setItem("kubero.JWT_TOKEN", response.data.access_token); + + const token = cookies.set("kubero.JWT_TOKEN", response.data.access_token); + window.location.href = "/" + }) + .catch((errors) => { + this.error = true; + console.log("Cannot log in"+errors) + }) } } }); - \ No newline at end of file + + + \ No newline at end of file diff --git a/client/src/components/pipelines/appcard.vue b/client/src/components/pipelines/appcard.vue index d10e252b..448141a6 100644 --- a/client/src/components/pipelines/appcard.vue +++ b/client/src/components/pipelines/appcard.vue @@ -254,7 +254,7 @@ export default defineComponent({ }) .then((result) => { if (result.isConfirmed) { - axios.delete(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app.name}`) + axios.delete(`/api/apps/${this.pipeline}/${this.phase}/${this.app.name}`) .then(response => { //this.$router.push(`/pipeline/${this.pipeline}/apps`); //console.log("deleteApp"); @@ -269,7 +269,7 @@ export default defineComponent({ }); }, async restartApp() { - axios.get(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app.name}/restart`) + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app.name}/restart`) .then(response => { //console.log(response); this.loadingState = true; @@ -283,7 +283,7 @@ export default defineComponent({ this.loadingState = false; }, loadMetrics() { - axios.get(`/api/metrics/${this.pipeline}/${this.phase}/${this.app.name}`) + axios.get(`/api/metrics/resources/${this.pipeline}/${this.phase}/${this.app.name}`) .then(response => { for (var i = 0; i < response.data.length; i++) { if (response.data[i].cpu.percentage != null && response.data[i].memory.percentage != null) { diff --git a/client/src/components/pipelines/detail.vue b/client/src/components/pipelines/detail.vue index 88e5edbb..54b7845c 100644 --- a/client/src/components/pipelines/detail.vue +++ b/client/src/components/pipelines/detail.vue @@ -136,6 +136,7 @@ async function loadPullrequests() { response.data.forEach((pr: Pullrequest) => { let found = false; phases.value[0].apps.forEach((app: App) => { + console.log(app.name, pr.branch); if (app.name == pr.branch) { found = true; } diff --git a/client/src/components/pipelines/form.vue b/client/src/components/pipelines/form.vue index 7a3badb2..6a3146d5 100644 --- a/client/src/components/pipelines/form.vue +++ b/client/src/components/pipelines/form.vue @@ -713,7 +713,7 @@ export default defineComponent({ this.buildpack = buildpack; }, getContextList() { - axios.get('/api/config/k8s/context').then(response => { + axios.get('/api/kubernetes/contexts').then(response => { for (let i = 0; i < response.data.length; i++) { this.contextList.push(response.data[i].name); } @@ -726,12 +726,12 @@ export default defineComponent({ }); }, listRepositories() { - axios.get('/api/config/repositories').then(response => { + axios.get('/api/repo/providers').then(response => { this.repositoriesList = response.data }); }, listBuildpacks() { - axios.get('/api/config/buildpacks').then(response => { + axios.get('/api/config/runpacks').then(response => { for (let i = 0; i < response.data.length; i++) { this.buildpackList.push({ text: response.data[i].name, @@ -805,7 +805,7 @@ export default defineComponent({ }, loadRepository() { - axios.get(`/api/repo/${this.repotab}/list`) + axios.get(`/api/repo/${this.repotab}/repositories`) .then(response => { this.gitrepoItems = response.data; }).catch(error => { @@ -939,7 +939,7 @@ export default defineComponent({ this.buildpack = this.buildpackList[0].value; } - axios.post(`/api/pipelines`, { + axios.post(`/api/pipelines/${this.pipeline}`, { pipelineName: this.pipelineName, domain: this.domain, gitrepo: this.gitrepo, diff --git a/client/src/components/pipelines/list.vue b/client/src/components/pipelines/list.vue index 59572957..76f82cff 100644 --- a/client/src/components/pipelines/list.vue +++ b/client/src/components/pipelines/list.vue @@ -146,7 +146,7 @@ type Pipeline = { const socket = useKuberoStore().kubero.socket as any; -socket.on('updatedPipelines', (instances: any) => { +socket.on('updatePipeline', (instances: any) => { //console.log("updatedPipelines", instances); loadPipelinesList(); }); diff --git a/client/src/components/pipelines/prcard.vue b/client/src/components/pipelines/prcard.vue index 938eec2b..1e34d8a7 100644 --- a/client/src/components/pipelines/prcard.vue +++ b/client/src/components/pipelines/prcard.vue @@ -95,7 +95,7 @@ export default defineComponent({ //console.log("startReviewApp", this.pullrequest.number); this.loadingState = true; - axios.post("/api/repo/pullrequest/start", { + axios.post("/api/apps/pullrequest", { branch: this.pullrequest.branch, title: this.pullrequest.title, ssh_url: this.pullrequest.ssh_url, diff --git a/client/src/components/settings/form-general.vue b/client/src/components/settings/form-general.vue index ea6b6984..81fef60a 100644 --- a/client/src/components/settings/form-general.vue +++ b/client/src/components/settings/form-general.vue @@ -380,7 +380,7 @@ export default defineComponent({ }, methods: { loadStorageClasses() { - axios.get('/api/config/storageclasses').then(response => { + axios.get('/api/kubernetes/storageclasses').then(response => { for (let i = 0; i < response.data.length; i++) { this.storageclasses.push(response.data[i].name); } diff --git a/client/src/components/settings/form.vue b/client/src/components/settings/form.vue index 1db734b2..46885328 100644 --- a/client/src/components/settings/form.vue +++ b/client/src/components/settings/form.vue @@ -527,7 +527,7 @@ export default defineComponent({ delete buildpack.advanced; }); - axios.post(`/api/settings`, self.settings) + axios.post(`/api/config`, self.settings) .then(response => { console.log('saveSettings', response); }) @@ -537,7 +537,7 @@ export default defineComponent({ }, async loadSettings() { const self = this; - axios.get(`/api/settings`) + axios.get(`/api/config`) .then(response => { self.settings = response.data; }) diff --git a/client/src/components/setup/index.vue b/client/src/components/setup/index.vue index a92e5fe4..08e45a39 100644 --- a/client/src/components/setup/index.vue +++ b/client/src/components/setup/index.vue @@ -422,7 +422,7 @@ users: save(obj: any) { axios.post('/api/config/setup/save', obj) .then((response) => { - if (response.status === 200 && response.data.status === 'ok') { + if (response.status === 201 && response.data.status === 'ok') { this.saveSuccess = 'ok' this.saveErrorMessage = '' this.checkInstalled('operator') @@ -503,12 +503,14 @@ users: return false } - const response = await axios.post('/api/config/k8s/kubeconfig/validate', { + const response = await axios.post('/api/config/setup/kubeconfig/validate', { kubeconfig: this.kubeConfig, context: this.kubeContext }) - if (response.status === 200) { + console.log(response.data) + + if (response.status === 201) { if (response.data.valid) { this.kubeconfigError = '' this.kubeconfigValid = true diff --git a/client/src/components/templates/index.vue b/client/src/components/templates/index.vue index 8a833640..7e3afeb5 100644 --- a/client/src/components/templates/index.vue +++ b/client/src/components/templates/index.vue @@ -245,6 +245,10 @@ type TemplatesList = { // }[] } +//Axios instance without Auth headers to load templates +const templates = axios.create(); +templates.defaults.headers.common = {}; + export default defineComponent({ sockets: { }, @@ -305,7 +309,7 @@ export default defineComponent({ }, loadCatalogs(catalogId: number) { const self = this; - axios.get(`/api/config/catalogs`) + axios.get(`/api/config/templates`) .then(response => { self.templates = response.data as Templates; if (self.templates.catalogs.length > 0 && self.templates.enabled == true) { @@ -338,7 +342,8 @@ export default defineComponent({ }, async loadTemplates(indexUrl: string) { const self = this; - axios.get(indexUrl) + + templates.get(indexUrl) .then(response => { self.templatesList = response.data; forEach(self.templatesList.categories, (value, key) => { diff --git a/client/src/layouts/default/AppBar.vue b/client/src/layouts/default/AppBar.vue index 7c2626b0..feeb1db0 100644 --- a/client/src/layouts/default/AppBar.vue +++ b/client/src/layouts/default/AppBar.vue @@ -25,7 +25,7 @@ export default defineComponent({ methods: { getBanner() { - axios.get('/api/banner').then((response: any) => { + axios.get('/api/config/banner').then((response: any) => { this.banner.show = response.data.show; this.banner.message = response.data.message; this.banner.bgcolor = response.data.bgcolor; diff --git a/client/src/layouts/default/NavDrawer.vue b/client/src/layouts/default/NavDrawer.vue index 51e82aac..f2c85234 100644 --- a/client/src/layouts/default/NavDrawer.vue +++ b/client/src/layouts/default/NavDrawer.vue @@ -196,11 +196,14 @@ theme.global.name.value = localStorage.getItem("theme") || 'light';