This commit is contained in:
21
.drone.yml
21
.drone.yml
@@ -26,16 +26,23 @@ steps:
|
|||||||
environment:
|
environment:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GOMODCACHE: '/drone/src/pkg.mod'
|
GOMODCACHE: '/drone/src/pkg.mod'
|
||||||
GOCACHE: '/drone/src/pkg.build'
|
GOCACHE: '/drone/src/pkg.build'
|
||||||
|
GOBIN: '/drone/src/pkg.tools'
|
||||||
volumes:
|
volumes:
|
||||||
- name: shared
|
- name: shared
|
||||||
path: /shared
|
path: /shared
|
||||||
commands:
|
commands:
|
||||||
#- cp /shared/index.html ./www/
|
- export PATH=/drone/src/pkg.tools:$PATH
|
||||||
#- go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
- go install github.com/a-h/templ/cmd/templ@latest
|
||||||
#- sqlc generate
|
- go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||||
- chmod +x .drone.sh
|
- go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
- ./.drone.sh
|
- sqlc generate
|
||||||
|
- templ generate -path ./components
|
||||||
|
- swag init --exclude "pkg.mod,pkg.build,pkg.tools" -o server/router/docs
|
||||||
|
- chmod +x ./scripts/*.sh
|
||||||
|
- ./scripts/update-swagger-ui.sh
|
||||||
|
- ./scripts/drone.sh
|
||||||
|
- cp ./build/cbs-linux-amd64 /shared/
|
||||||
|
|
||||||
- name: dell-sftp-deploy
|
- name: dell-sftp-deploy
|
||||||
image: hypervtechnics/drone-sftp
|
image: hypervtechnics/drone-sftp
|
||||||
@@ -78,4 +85,4 @@ volumes:
|
|||||||
temp: {}
|
temp: {}
|
||||||
- name: cache
|
- name: cache
|
||||||
host:
|
host:
|
||||||
path: /var/lib/cache
|
path: /var/lib/cache
|
||||||
|
|||||||
92
.github/workflows/ci.yml
vendored
92
.github/workflows/ci.yml
vendored
@@ -1,92 +0,0 @@
|
|||||||
name: CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths-ignore:
|
|
||||||
- '.github/**'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: Lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: 1.22.x
|
|
||||||
- run: go mod download
|
|
||||||
- run: go install github.com/a-h/templ/cmd/templ@v0.2.771
|
|
||||||
- run: make generate-templ
|
|
||||||
- uses: sqlc-dev/setup-sqlc@v4
|
|
||||||
with:
|
|
||||||
sqlc-version: '1.27.0'
|
|
||||||
- run: sqlc vet
|
|
||||||
- run: sqlc generate
|
|
||||||
- name: Lint
|
|
||||||
uses: golangci/golangci-lint-action@v3
|
|
||||||
with:
|
|
||||||
version: v1.54
|
|
||||||
skip-pkg-cache: true
|
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: 1.22.x
|
|
||||||
- run: go mod download
|
|
||||||
- run: go install github.com/a-h/templ/cmd/templ@v0.2.771
|
|
||||||
- run: make generate-templ
|
|
||||||
- uses: sqlc-dev/setup-sqlc@v4
|
|
||||||
with:
|
|
||||||
sqlc-version: '1.27.0'
|
|
||||||
- run: sqlc generate
|
|
||||||
- name: Test
|
|
||||||
run: go test -race ./...
|
|
||||||
e2e:
|
|
||||||
name: End-to-End
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: 1.22.x
|
|
||||||
- run: go mod download
|
|
||||||
- run: go install github.com/a-h/templ/cmd/templ@v0.2.771
|
|
||||||
- run: templ generate -path ./components
|
|
||||||
- uses: sqlc-dev/setup-sqlc@v4
|
|
||||||
with:
|
|
||||||
sqlc-version: '1.27.0'
|
|
||||||
- run: sqlc generate
|
|
||||||
- run: go test ./... -tags=e2e
|
|
||||||
docker-publish:
|
|
||||||
name: Publish Docker
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
needs:
|
|
||||||
- lint
|
|
||||||
- test
|
|
||||||
- e2e
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- uses: docker/metadata-action@v5
|
|
||||||
id: meta
|
|
||||||
with:
|
|
||||||
images: ghcr.io/piszmog/vctp
|
|
||||||
- uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
77
.github/workflows/release.yml
vendored
77
.github/workflows/release.yml
vendored
@@ -1,77 +0,0 @@
|
|||||||
name: Release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: The version to release (e.g. v1.0.0)
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: 1.22.x
|
|
||||||
- run: go mod download
|
|
||||||
- run: go install github.com/a-h/templ/cmd/templ@v0.2.771
|
|
||||||
- name: Generate Templ Files
|
|
||||||
run: make generate-templ
|
|
||||||
- name: Generate CSS
|
|
||||||
run: |
|
|
||||||
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
|
|
||||||
chmod +x tailwindcss-linux-x64
|
|
||||||
mv tailwindcss-linux-x64 tailwindcss
|
|
||||||
./tailwindcss -i ./styles/input.css -o ./dist/assets/css/output@${{ github.event.inputs.version }}.css --minify
|
|
||||||
- uses: sqlc-dev/setup-sqlc@v4
|
|
||||||
with:
|
|
||||||
sqlc-version: '1.27.0'
|
|
||||||
- run: sqlc generate
|
|
||||||
- name: Build Application
|
|
||||||
run: go build -o ./app -ldflags="-s -w -X version.Value=${{ github.event.inputs.version }}"
|
|
||||||
- name: Create Tag
|
|
||||||
uses: piszmog/create-tag@v1
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version }}
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
name: ${{ github.event.inputs.version }}
|
|
||||||
tag_name: ${{ github.event.inputs.version }}
|
|
||||||
generate_release_notes: true
|
|
||||||
files: app
|
|
||||||
publish:
|
|
||||||
name: Publish Docker
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
needs:
|
|
||||||
- release
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- uses: docker/metadata-action@v5
|
|
||||||
id: meta
|
|
||||||
with:
|
|
||||||
images: ghcr.io/piszmog/my-app
|
|
||||||
tags: |
|
|
||||||
type=raw,value=${{ github.event.inputs.version }}
|
|
||||||
- uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
build-args: |
|
|
||||||
VERSION=$${{ github.event.inputs.version }}
|
|
||||||
@@ -100,7 +100,7 @@ This is where `templ` files live. Anything you want to render to the user goes h
|
|||||||
### DB
|
### DB
|
||||||
|
|
||||||
This is the directory that `sqlc` generates to. Update `queries.sql` to build
|
This is the directory that `sqlc` generates to. Update `queries.sql` to build
|
||||||
your database operations.
|
your database operations. The schema for `sqlc` lives in `db/schema.sql`.
|
||||||
|
|
||||||
Once `queries.sql` is updated, run `make generate-sql` to update the generated models
|
Once `queries.sql` is updated, run `make generate-sql` to update the generated models
|
||||||
|
|
||||||
@@ -133,6 +133,12 @@ DB_DRIVER=postgres DB_URL=postgres://user:pass@localhost:5432/vctp?sslmode=disab
|
|||||||
PostgreSQL migrations live in `db/migrations_postgres`, while SQLite migrations remain in
|
PostgreSQL migrations live in `db/migrations_postgres`, while SQLite migrations remain in
|
||||||
`db/migrations`.
|
`db/migrations`.
|
||||||
|
|
||||||
|
#### Snapshot Retention
|
||||||
|
Hourly and daily snapshot table retention can be configured with environment variables:
|
||||||
|
|
||||||
|
- `HOURLY_SNAPSHOT_MAX_AGE_DAYS` (default: 60)
|
||||||
|
- `DAILY_SNAPSHOT_MAX_AGE_MONTHS` (default: 12)
|
||||||
|
|
||||||
### Dist
|
### Dist
|
||||||
|
|
||||||
This is where your assets live. Any Javascript, images, or styling needs to go in the
|
This is where your assets live. Any Javascript, images, or styling needs to go in the
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.2.778
|
// templ: version: v0.3.977
|
||||||
package core
|
package core
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
@@ -29,11 +29,11 @@ func Footer() templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"fixed p-1 bottom-0 bg-gray-100 w-full border-t\"><div class=\"rounded-lg p-4 text-xs italic text-gray-700 text-center\">© Nathan Coad (nathan.coad@dell.com)</div></footer>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<footer class=\"fixed p-1 bottom-0 bg-gray-100 w-full border-t\"><div class=\"rounded-lg p-4 text-xs italic text-gray-700 text-center\">© Nathan Coad (nathan.coad@dell.com)</div></footer>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
return templ_7745c5c3_Err
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.2.778
|
// templ: version: v0.3.977
|
||||||
package core
|
package core
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
@@ -31,24 +31,24 @@ func Header() templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"vCTP API endpoint\"><title>vCTP API</title><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><link href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"vCTP API endpoint\"><title>vCTP API</title><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><link href=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 templ.SafeURL
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/assets/css/output@" + version.Value + ".css")
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs("/assets/css/output@" + version.Value + ".css")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/header.templ`, Line: 12, Col: 61}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/core/header.templ`, Line: 12, Col: 61}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" rel=\"stylesheet\"></head>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" rel=\"stylesheet\"></head>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
return templ_7745c5c3_Err
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.2.778
|
// templ: version: v0.3.977
|
||||||
package views
|
package views
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
@@ -39,7 +39,7 @@ func Index(info BuildInfo) templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -47,46 +47,46 @@ func Index(info BuildInfo) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"flex flex-col min-h-screen\"><main class=\"flex-grow\"><div><h1 class=\"text-5xl font-bold\">Build Information</h1><p class=\"mt-4\"><strong>Build Time:</strong> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen\"><main class=\"flex-grow\"><div><h1 class=\"text-5xl font-bold\">Build Information</h1><p class=\"mt-4\"><strong>Build Time:</strong> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(info.BuildTime)
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(info.BuildTime)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 21, Col: 80}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 21, Col: 80}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p class=\"mt-4\"><strong>SHA1 Version:</strong> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p><p class=\"mt-4\"><strong>SHA1 Version:</strong> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var3 string
|
var templ_7745c5c3_Var3 string
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(info.SHA1Ver)
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(info.SHA1Ver)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 22, Col: 80}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 22, Col: 80}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p class=\"mt-4\"><strong>Go Runtime Version:</strong> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><p class=\"mt-4\"><strong>Go Runtime Version:</strong> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var4 string
|
var templ_7745c5c3_Var4 string
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(info.GoVersion)
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(info.GoVersion)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 23, Col: 88}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 23, Col: 88}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></main></body>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p></div></main></body>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -94,11 +94,11 @@ func Index(info BuildInfo) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
return templ_7745c5c3_Err
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
47
components/views/snapshots.templ
Normal file
47
components/views/snapshots.templ
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"vctp/components/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnapshotEntry struct {
|
||||||
|
Label string
|
||||||
|
Link string
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SnapshotHourlyList(entries []SnapshotEntry) {
|
||||||
|
@SnapshotListPage("Hourly Inventory Snapshots", "inventory snapshots captured hourly", entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SnapshotDailyList(entries []SnapshotEntry) {
|
||||||
|
@SnapshotListPage("Daily Inventory Snapshots", "daily summaries of hourly inventory snapshots", entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SnapshotMonthlyList(entries []SnapshotEntry) {
|
||||||
|
@SnapshotListPage("Monthly Inventory Snapshots", "monthly summary aggregated from daily snapshots", entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
@core.Header()
|
||||||
|
<body class="flex flex-col min-h-screen">
|
||||||
|
<main class="flex-grow">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-4xl font-bold">{title}</h1>
|
||||||
|
<p class="mt-2 text-gray-600">{subtitle}</p>
|
||||||
|
<ul class="mt-6 space-y-2">
|
||||||
|
for _, entry := range entries {
|
||||||
|
<li>
|
||||||
|
<a class="text-blue-600 hover:underline" href={entry.Link}>
|
||||||
|
{entry.Label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
@core.Footer()
|
||||||
|
</html>
|
||||||
|
}
|
||||||
214
components/views/snapshots_templ.go
Normal file
214
components/views/snapshots_templ.go
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.977
|
||||||
|
package views
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"vctp/components/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnapshotEntry struct {
|
||||||
|
Label string
|
||||||
|
Link string
|
||||||
|
}
|
||||||
|
|
||||||
|
func SnapshotHourlyList(entries []SnapshotEntry) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = SnapshotListPage("Hourly Inventory Snapshots", "inventory snapshots captured hourly", entries).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SnapshotDailyList(entries []SnapshotEntry) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var2 == nil {
|
||||||
|
templ_7745c5c3_Var2 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = SnapshotListPage("Daily Inventory Snapshots", "daily summaries of hourly inventory snapshots", entries).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SnapshotMonthlyList(entries []SnapshotEntry) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var3 == nil {
|
||||||
|
templ_7745c5c3_Var3 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = SnapshotListPage("Monthly Inventory Snapshots", "monthly summary aggregated from daily snapshots", entries).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var4 == nil {
|
||||||
|
templ_7745c5c3_Var4 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.Header().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen\"><main class=\"flex-grow\"><div><h1 class=\"text-4xl font-bold\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 31, Col: 42}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1><p class=\"mt-2 text-gray-600\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(subtitle)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 32, Col: 44}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><ul class=\"mt-6 space-y-2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<li><a class=\"text-blue-600 hover:underline\" href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(entry.Link)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 36, Col: 65}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Label)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 37, Col: 21}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</a></li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</ul></div></main></body>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.Footer().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
@@ -6,17 +6,17 @@ ALTER TABLE "Inventory" RENAME COLUMN SrmPlaceholder TO SrmPlaceholder_old;
|
|||||||
ALTER TABLE "Inventory" ADD COLUMN IsTemplate TEXT NOT NULL DEFAULT "FALSE";
|
ALTER TABLE "Inventory" ADD COLUMN IsTemplate TEXT NOT NULL DEFAULT "FALSE";
|
||||||
ALTER TABLE "Inventory" ADD COLUMN PoweredOn TEXT NOT NULL DEFAULT "FALSE";
|
ALTER TABLE "Inventory" ADD COLUMN PoweredOn TEXT NOT NULL DEFAULT "FALSE";
|
||||||
ALTER TABLE "Inventory" ADD COLUMN SrmPlaceholder TEXT NOT NULL DEFAULT "FALSE";
|
ALTER TABLE "Inventory" ADD COLUMN SrmPlaceholder TEXT NOT NULL DEFAULT "FALSE";
|
||||||
UPDATE Inventory
|
UPDATE "Inventory"
|
||||||
SET IsTemplate = CASE
|
SET IsTemplate = CASE
|
||||||
WHEN IsTemplate_old = 1 THEN 'TRUE'
|
WHEN IsTemplate_old = 1 THEN 'TRUE'
|
||||||
ELSE 'FALSE'
|
ELSE 'FALSE'
|
||||||
END;
|
END;
|
||||||
UPDATE Inventory
|
UPDATE "Inventory"
|
||||||
SET PoweredOn = CASE
|
SET PoweredOn = CASE
|
||||||
WHEN PowerState_old = 1 THEN 'TRUE'
|
WHEN PowerState_old = 1 THEN 'TRUE'
|
||||||
ELSE 'FALSE'
|
ELSE 'FALSE'
|
||||||
END;
|
END;
|
||||||
UPDATE Inventory
|
UPDATE "Inventory"
|
||||||
SET SrmPlaceholder = CASE
|
SET SrmPlaceholder = CASE
|
||||||
WHEN SrmPlaceholder_old = 1 THEN 'TRUE'
|
WHEN SrmPlaceholder_old = 1 THEN 'TRUE'
|
||||||
ELSE 'FALSE'
|
ELSE 'FALSE'
|
||||||
@@ -35,17 +35,17 @@ ALTER TABLE "Inventory" RENAME COLUMN SrmPlaceholder TO SrmPlaceholder_old;
|
|||||||
ALTER TABLE "Inventory" ADD COLUMN IsTemplate INTEGER;
|
ALTER TABLE "Inventory" ADD COLUMN IsTemplate INTEGER;
|
||||||
ALTER TABLE "Inventory" ADD COLUMN PowerState INTEGER;
|
ALTER TABLE "Inventory" ADD COLUMN PowerState INTEGER;
|
||||||
ALTER TABLE "Inventory" ADD COLUMN SrmPlaceholder INTEGER;
|
ALTER TABLE "Inventory" ADD COLUMN SrmPlaceholder INTEGER;
|
||||||
UPDATE Inventory
|
UPDATE "Inventory"
|
||||||
SET IsTemplate = CASE
|
SET IsTemplate = CASE
|
||||||
WHEN IsTemplate_old = 'TRUE' THEN 1
|
WHEN IsTemplate_old = 'TRUE' THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END;
|
END;
|
||||||
UPDATE Inventory
|
UPDATE "Inventory"
|
||||||
SET PowerState = CASE
|
SET PowerState = CASE
|
||||||
WHEN PoweredOn_old = 'TRUE' THEN 1
|
WHEN PoweredOn_old = 'TRUE' THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END;
|
END;
|
||||||
UPDATE Inventory
|
UPDATE "Inventory"
|
||||||
SET SrmPlaceholder = CASE
|
SET SrmPlaceholder = CASE
|
||||||
WHEN SrmPlaceholder_old = 'TRUE' THEN 1
|
WHEN SrmPlaceholder_old = 'TRUE' THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.27.0
|
// sqlc v1.29.0
|
||||||
|
|
||||||
package queries
|
package queries
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.27.0
|
// sqlc v1.29.0
|
||||||
|
|
||||||
package queries
|
package queries
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Events struct {
|
type Event struct {
|
||||||
Eid int64
|
Eid int64
|
||||||
CloudId string
|
CloudId string
|
||||||
Source string
|
Source string
|
||||||
@@ -60,7 +60,7 @@ type InventoryHistory struct {
|
|||||||
PreviousProvisionedDisk sql.NullFloat64
|
PreviousProvisionedDisk sql.NullFloat64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Updates struct {
|
type Update struct {
|
||||||
Uid int64
|
Uid int64
|
||||||
InventoryId sql.NullInt64
|
InventoryId sql.NullInt64
|
||||||
UpdateTime sql.NullInt64
|
UpdateTime sql.NullInt64
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
-- name: ListInventory :many
|
-- name: ListInventory :many
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
ORDER BY "Name";
|
ORDER BY "Name";
|
||||||
|
|
||||||
-- name: GetReportInventory :many
|
-- name: GetReportInventory :many
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
ORDER BY "CreationTime";
|
ORDER BY "CreationTime";
|
||||||
|
|
||||||
-- name: GetInventoryByName :many
|
-- name: GetInventoryByName :many
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
WHERE "Name" = ?;
|
WHERE "Name" = ?;
|
||||||
|
|
||||||
-- name: GetInventoryByVcenter :many
|
-- name: GetInventoryByVcenter :many
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
WHERE "Vcenter" = ?;
|
WHERE "Vcenter" = ?;
|
||||||
|
|
||||||
-- name: GetInventoryVmId :one
|
-- name: GetInventoryVmId :one
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
|
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
|
||||||
|
|
||||||
-- name: GetInventoryVmUuid :one
|
-- name: GetInventoryVmUuid :one
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
WHERE "VmUuid" = sqlc.arg('vmUuid') AND "Datacenter" = sqlc.arg('datacenterName');
|
WHERE "VmUuid" = sqlc.arg('vmUuid') AND "Datacenter" = sqlc.arg('datacenterName');
|
||||||
|
|
||||||
-- name: GetInventoryVcUrl :many
|
-- name: GetInventoryVcUrl :many
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
WHERE "Vcenter" = sqlc.arg('vc');
|
WHERE "Vcenter" = sqlc.arg('vc');
|
||||||
|
|
||||||
-- name: GetInventoryEventId :one
|
-- name: GetInventoryEventId :one
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM inventory
|
||||||
WHERE "CloudId" = ? LIMIT 1;
|
WHERE "CloudId" = ? LIMIT 1;
|
||||||
|
|
||||||
-- name: CreateInventory :one
|
-- name: CreateInventory :one
|
||||||
INSERT INTO "Inventory" (
|
INSERT INTO inventory (
|
||||||
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
|
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
@@ -39,32 +39,32 @@ INSERT INTO "Inventory" (
|
|||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: InventoryUpdate :exec
|
-- name: InventoryUpdate :exec
|
||||||
UPDATE "Inventory"
|
UPDATE inventory
|
||||||
SET "VmUuid" = sqlc.arg('uuid'), "SrmPlaceholder" = sqlc.arg('srmPlaceholder')
|
SET "VmUuid" = sqlc.arg('uuid'), "SrmPlaceholder" = sqlc.arg('srmPlaceholder')
|
||||||
WHERE "Iid" = sqlc.arg('iid');
|
WHERE "Iid" = sqlc.arg('iid');
|
||||||
|
|
||||||
-- name: InventoryMarkDeleted :exec
|
-- name: InventoryMarkDeleted :exec
|
||||||
UPDATE "Inventory"
|
UPDATE inventory
|
||||||
SET "DeletionTime" = sqlc.arg('deletionTime')
|
SET "DeletionTime" = sqlc.arg('deletionTime')
|
||||||
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
|
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
|
||||||
|
|
||||||
-- name: InventoryCleanup :exec
|
-- name: InventoryCleanup :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM inventory
|
||||||
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName')
|
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName')
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: InventoryCleanupVcenter :exec
|
-- name: InventoryCleanupVcenter :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM inventory
|
||||||
WHERE "Vcenter" = sqlc.arg('vc')
|
WHERE "Vcenter" = sqlc.arg('vc')
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: InventoryCleanupTemplates :exec
|
-- name: InventoryCleanupTemplates :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM inventory
|
||||||
WHERE "IsTemplate" = 'TRUE'
|
WHERE "IsTemplate" = 'TRUE'
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateUpdate :one
|
-- name: CreateUpdate :one
|
||||||
INSERT INTO "Updates" (
|
INSERT INTO updates (
|
||||||
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
|
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
@@ -72,25 +72,25 @@ INSERT INTO "Updates" (
|
|||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: GetReportUpdates :many
|
-- name: GetReportUpdates :many
|
||||||
SELECT * FROM "Updates"
|
SELECT * FROM updates
|
||||||
ORDER BY "UpdateTime";
|
ORDER BY "UpdateTime";
|
||||||
|
|
||||||
-- name: GetVmUpdates :many
|
-- name: GetVmUpdates :many
|
||||||
SELECT * FROM "Updates"
|
SELECT * FROM updates
|
||||||
WHERE "UpdateType" = sqlc.arg('updateType') AND "InventoryId" = sqlc.arg('InventoryId');
|
WHERE "UpdateType" = sqlc.arg('updateType') AND "InventoryId" = sqlc.arg('InventoryId');
|
||||||
|
|
||||||
-- name: CleanupUpdates :exec
|
-- name: CleanupUpdates :exec
|
||||||
DELETE FROM "Updates"
|
DELETE FROM updates
|
||||||
WHERE "UpdateType" = sqlc.arg('updateType') AND "UpdateTime" <= sqlc.arg('updateTime')
|
WHERE "UpdateType" = sqlc.arg('updateType') AND "UpdateTime" <= sqlc.arg('updateTime')
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CleanupUpdatesNullVm :exec
|
-- name: CleanupUpdatesNullVm :exec
|
||||||
DELETE FROM "Updates"
|
DELETE FROM updates
|
||||||
WHERE "InventoryId" IS NULL
|
WHERE "InventoryId" IS NULL
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateEvent :one
|
-- name: CreateEvent :one
|
||||||
INSERT INTO "Events" (
|
INSERT INTO events (
|
||||||
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
@@ -98,22 +98,22 @@ INSERT INTO "Events" (
|
|||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: ListEvents :many
|
-- name: ListEvents :many
|
||||||
SELECT * FROM "Events"
|
SELECT * FROM events
|
||||||
ORDER BY "EventTime";
|
ORDER BY "EventTime";
|
||||||
|
|
||||||
-- name: ListUnprocessedEvents :many
|
-- name: ListUnprocessedEvents :many
|
||||||
SELECT * FROM "Events"
|
SELECT * FROM events
|
||||||
WHERE "Processed" = 0
|
WHERE "Processed" = 0
|
||||||
AND "EventTime" > sqlc.arg('eventTime')
|
AND "EventTime" > sqlc.arg('eventTime')
|
||||||
ORDER BY "EventTime";
|
ORDER BY "EventTime";
|
||||||
|
|
||||||
-- name: UpdateEventsProcessed :exec
|
-- name: UpdateEventsProcessed :exec
|
||||||
UPDATE "Events"
|
UPDATE events
|
||||||
SET "Processed" = 1
|
SET "Processed" = 1
|
||||||
WHERE "Eid" = sqlc.arg('eid');
|
WHERE "Eid" = sqlc.arg('eid');
|
||||||
|
|
||||||
-- name: CreateInventoryHistory :one
|
-- name: CreateInventoryHistory :one
|
||||||
INSERT INTO "InventoryHistory" (
|
INSERT INTO inventory_history (
|
||||||
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
|
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.27.0
|
// sqlc v1.29.0
|
||||||
// source: query.sql
|
// source: query.sql
|
||||||
|
|
||||||
package queries
|
package queries
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const cleanupUpdates = `-- name: CleanupUpdates :exec
|
const cleanupUpdates = `-- name: CleanupUpdates :exec
|
||||||
DELETE FROM "Updates"
|
DELETE FROM updates
|
||||||
WHERE "UpdateType" = ?1 AND "UpdateTime" <= ?2
|
WHERE "UpdateType" = ?1 AND "UpdateTime" <= ?2
|
||||||
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
|
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
|
||||||
`
|
`
|
||||||
@@ -27,7 +27,7 @@ func (q *Queries) CleanupUpdates(ctx context.Context, arg CleanupUpdatesParams)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanupUpdatesNullVm = `-- name: CleanupUpdatesNullVm :exec
|
const cleanupUpdatesNullVm = `-- name: CleanupUpdatesNullVm :exec
|
||||||
DELETE FROM "Updates"
|
DELETE FROM updates
|
||||||
WHERE "InventoryId" IS NULL
|
WHERE "InventoryId" IS NULL
|
||||||
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
|
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
|
||||||
`
|
`
|
||||||
@@ -38,7 +38,7 @@ func (q *Queries) CleanupUpdatesNullVm(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createEvent = `-- name: CreateEvent :one
|
const createEvent = `-- name: CreateEvent :one
|
||||||
INSERT INTO "Events" (
|
INSERT INTO events (
|
||||||
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
@@ -62,7 +62,7 @@ type CreateEventParams struct {
|
|||||||
UserName sql.NullString
|
UserName sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Events, error) {
|
func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event, error) {
|
||||||
row := q.db.QueryRowContext(ctx, createEvent,
|
row := q.db.QueryRowContext(ctx, createEvent,
|
||||||
arg.CloudId,
|
arg.CloudId,
|
||||||
arg.Source,
|
arg.Source,
|
||||||
@@ -78,7 +78,7 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
|
|||||||
arg.ComputeResourceName,
|
arg.ComputeResourceName,
|
||||||
arg.UserName,
|
arg.UserName,
|
||||||
)
|
)
|
||||||
var i Events
|
var i Event
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Eid,
|
&i.Eid,
|
||||||
&i.CloudId,
|
&i.CloudId,
|
||||||
@@ -100,7 +100,7 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createInventory = `-- name: CreateInventory :one
|
const createInventory = `-- name: CreateInventory :one
|
||||||
INSERT INTO "Inventory" (
|
INSERT INTO inventory (
|
||||||
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
|
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
@@ -177,7 +177,7 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createInventoryHistory = `-- name: CreateInventoryHistory :one
|
const createInventoryHistory = `-- name: CreateInventoryHistory :one
|
||||||
INSERT INTO "InventoryHistory" (
|
INSERT INTO inventory_history (
|
||||||
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
|
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?
|
||||||
@@ -220,7 +220,7 @@ func (q *Queries) CreateInventoryHistory(ctx context.Context, arg CreateInventor
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createUpdate = `-- name: CreateUpdate :one
|
const createUpdate = `-- name: CreateUpdate :one
|
||||||
INSERT INTO "Updates" (
|
INSERT INTO updates (
|
||||||
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
|
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
|
||||||
) VALUES(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
@@ -244,7 +244,7 @@ type CreateUpdateParams struct {
|
|||||||
RawChangeString []byte
|
RawChangeString []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Updates, error) {
|
func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Update, error) {
|
||||||
row := q.db.QueryRowContext(ctx, createUpdate,
|
row := q.db.QueryRowContext(ctx, createUpdate,
|
||||||
arg.InventoryId,
|
arg.InventoryId,
|
||||||
arg.Name,
|
arg.Name,
|
||||||
@@ -260,7 +260,7 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
|
|||||||
arg.PlaceholderChange,
|
arg.PlaceholderChange,
|
||||||
arg.RawChangeString,
|
arg.RawChangeString,
|
||||||
)
|
)
|
||||||
var i Updates
|
var i Update
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Uid,
|
&i.Uid,
|
||||||
&i.InventoryId,
|
&i.InventoryId,
|
||||||
@@ -281,7 +281,7 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryByName = `-- name: GetInventoryByName :many
|
const getInventoryByName = `-- name: GetInventoryByName :many
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
WHERE "Name" = ?
|
WHERE "Name" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -330,7 +330,7 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryByVcenter = `-- name: GetInventoryByVcenter :many
|
const getInventoryByVcenter = `-- name: GetInventoryByVcenter :many
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
WHERE "Vcenter" = ?
|
WHERE "Vcenter" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ func (q *Queries) GetInventoryByVcenter(ctx context.Context, vcenter string) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryEventId = `-- name: GetInventoryEventId :one
|
const getInventoryEventId = `-- name: GetInventoryEventId :one
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
WHERE "CloudId" = ? LIMIT 1
|
WHERE "CloudId" = ? LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -412,7 +412,7 @@ func (q *Queries) GetInventoryEventId(ctx context.Context, cloudid sql.NullStrin
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryVcUrl = `-- name: GetInventoryVcUrl :many
|
const getInventoryVcUrl = `-- name: GetInventoryVcUrl :many
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
WHERE "Vcenter" = ?1
|
WHERE "Vcenter" = ?1
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -461,7 +461,7 @@ func (q *Queries) GetInventoryVcUrl(ctx context.Context, vc string) ([]Inventory
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryVmId = `-- name: GetInventoryVmId :one
|
const getInventoryVmId = `-- name: GetInventoryVmId :one
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
WHERE "VmId" = ?1 AND "Datacenter" = ?2
|
WHERE "VmId" = ?1 AND "Datacenter" = ?2
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ func (q *Queries) GetInventoryVmId(ctx context.Context, arg GetInventoryVmIdPara
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryVmUuid = `-- name: GetInventoryVmUuid :one
|
const getInventoryVmUuid = `-- name: GetInventoryVmUuid :one
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
WHERE "VmUuid" = ?1 AND "Datacenter" = ?2
|
WHERE "VmUuid" = ?1 AND "Datacenter" = ?2
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -537,7 +537,7 @@ func (q *Queries) GetInventoryVmUuid(ctx context.Context, arg GetInventoryVmUuid
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getReportInventory = `-- name: GetReportInventory :many
|
const getReportInventory = `-- name: GetReportInventory :many
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
ORDER BY "CreationTime"
|
ORDER BY "CreationTime"
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -586,19 +586,19 @@ func (q *Queries) GetReportInventory(ctx context.Context) ([]Inventory, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getReportUpdates = `-- name: GetReportUpdates :many
|
const getReportUpdates = `-- name: GetReportUpdates :many
|
||||||
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM "Updates"
|
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM updates
|
||||||
ORDER BY "UpdateTime"
|
ORDER BY "UpdateTime"
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetReportUpdates(ctx context.Context) ([]Updates, error) {
|
func (q *Queries) GetReportUpdates(ctx context.Context) ([]Update, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, getReportUpdates)
|
rows, err := q.db.QueryContext(ctx, getReportUpdates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []Updates
|
var items []Update
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i Updates
|
var i Update
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Uid,
|
&i.Uid,
|
||||||
&i.InventoryId,
|
&i.InventoryId,
|
||||||
@@ -629,7 +629,7 @@ func (q *Queries) GetReportUpdates(ctx context.Context) ([]Updates, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getVmUpdates = `-- name: GetVmUpdates :many
|
const getVmUpdates = `-- name: GetVmUpdates :many
|
||||||
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM "Updates"
|
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM updates
|
||||||
WHERE "UpdateType" = ?1 AND "InventoryId" = ?2
|
WHERE "UpdateType" = ?1 AND "InventoryId" = ?2
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -638,15 +638,15 @@ type GetVmUpdatesParams struct {
|
|||||||
InventoryId sql.NullInt64
|
InventoryId sql.NullInt64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetVmUpdates(ctx context.Context, arg GetVmUpdatesParams) ([]Updates, error) {
|
func (q *Queries) GetVmUpdates(ctx context.Context, arg GetVmUpdatesParams) ([]Update, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, getVmUpdates, arg.UpdateType, arg.InventoryId)
|
rows, err := q.db.QueryContext(ctx, getVmUpdates, arg.UpdateType, arg.InventoryId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []Updates
|
var items []Update
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i Updates
|
var i Update
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Uid,
|
&i.Uid,
|
||||||
&i.InventoryId,
|
&i.InventoryId,
|
||||||
@@ -677,7 +677,7 @@ func (q *Queries) GetVmUpdates(ctx context.Context, arg GetVmUpdatesParams) ([]U
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inventoryCleanup = `-- name: InventoryCleanup :exec
|
const inventoryCleanup = `-- name: InventoryCleanup :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM inventory
|
||||||
WHERE "VmId" = ?1 AND "Datacenter" = ?2
|
WHERE "VmId" = ?1 AND "Datacenter" = ?2
|
||||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||||
`
|
`
|
||||||
@@ -693,7 +693,7 @@ func (q *Queries) InventoryCleanup(ctx context.Context, arg InventoryCleanupPara
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inventoryCleanupTemplates = `-- name: InventoryCleanupTemplates :exec
|
const inventoryCleanupTemplates = `-- name: InventoryCleanupTemplates :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM inventory
|
||||||
WHERE "IsTemplate" = 'TRUE'
|
WHERE "IsTemplate" = 'TRUE'
|
||||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||||
`
|
`
|
||||||
@@ -704,7 +704,7 @@ func (q *Queries) InventoryCleanupTemplates(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inventoryCleanupVcenter = `-- name: InventoryCleanupVcenter :exec
|
const inventoryCleanupVcenter = `-- name: InventoryCleanupVcenter :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM inventory
|
||||||
WHERE "Vcenter" = ?1
|
WHERE "Vcenter" = ?1
|
||||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||||
`
|
`
|
||||||
@@ -715,7 +715,7 @@ func (q *Queries) InventoryCleanupVcenter(ctx context.Context, vc string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inventoryMarkDeleted = `-- name: InventoryMarkDeleted :exec
|
const inventoryMarkDeleted = `-- name: InventoryMarkDeleted :exec
|
||||||
UPDATE "Inventory"
|
UPDATE inventory
|
||||||
SET "DeletionTime" = ?1
|
SET "DeletionTime" = ?1
|
||||||
WHERE "VmId" = ?2 AND "Datacenter" = ?3
|
WHERE "VmId" = ?2 AND "Datacenter" = ?3
|
||||||
`
|
`
|
||||||
@@ -732,7 +732,7 @@ func (q *Queries) InventoryMarkDeleted(ctx context.Context, arg InventoryMarkDel
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inventoryUpdate = `-- name: InventoryUpdate :exec
|
const inventoryUpdate = `-- name: InventoryUpdate :exec
|
||||||
UPDATE "Inventory"
|
UPDATE inventory
|
||||||
SET "VmUuid" = ?1, "SrmPlaceholder" = ?2
|
SET "VmUuid" = ?1, "SrmPlaceholder" = ?2
|
||||||
WHERE "Iid" = ?3
|
WHERE "Iid" = ?3
|
||||||
`
|
`
|
||||||
@@ -749,19 +749,19 @@ func (q *Queries) InventoryUpdate(ctx context.Context, arg InventoryUpdateParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listEvents = `-- name: ListEvents :many
|
const listEvents = `-- name: ListEvents :many
|
||||||
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events"
|
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM events
|
||||||
ORDER BY "EventTime"
|
ORDER BY "EventTime"
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
|
func (q *Queries) ListEvents(ctx context.Context) ([]Event, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, listEvents)
|
rows, err := q.db.QueryContext(ctx, listEvents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []Events
|
var items []Event
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i Events
|
var i Event
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Eid,
|
&i.Eid,
|
||||||
&i.CloudId,
|
&i.CloudId,
|
||||||
@@ -793,7 +793,7 @@ func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listInventory = `-- name: ListInventory :many
|
const listInventory = `-- name: ListInventory :many
|
||||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM inventory
|
||||||
ORDER BY "Name"
|
ORDER BY "Name"
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -842,21 +842,21 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listUnprocessedEvents = `-- name: ListUnprocessedEvents :many
|
const listUnprocessedEvents = `-- name: ListUnprocessedEvents :many
|
||||||
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events"
|
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM events
|
||||||
WHERE "Processed" = 0
|
WHERE "Processed" = 0
|
||||||
AND "EventTime" > ?1
|
AND "EventTime" > ?1
|
||||||
ORDER BY "EventTime"
|
ORDER BY "EventTime"
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]Events, error) {
|
func (q *Queries) ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]Event, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents, eventtime)
|
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents, eventtime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []Events
|
var items []Event
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i Events
|
var i Event
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Eid,
|
&i.Eid,
|
||||||
&i.CloudId,
|
&i.CloudId,
|
||||||
@@ -888,7 +888,7 @@ func (q *Queries) ListUnprocessedEvents(ctx context.Context, eventtime sql.NullI
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateEventsProcessed = `-- name: UpdateEventsProcessed :exec
|
const updateEventsProcessed = `-- name: UpdateEventsProcessed :exec
|
||||||
UPDATE "Events"
|
UPDATE events
|
||||||
SET "Processed" = 1
|
SET "Processed" = 1
|
||||||
WHERE "Eid" = ?1
|
WHERE "Eid" = ?1
|
||||||
`
|
`
|
||||||
|
|||||||
68
db/schema.sql
Normal file
68
db/schema.sql
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS inventory (
|
||||||
|
"Iid" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"Vcenter" TEXT NOT NULL,
|
||||||
|
"VmId" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"CloudId" TEXT,
|
||||||
|
"CreationTime" INTEGER,
|
||||||
|
"DeletionTime" INTEGER,
|
||||||
|
"ResourcePool" TEXT,
|
||||||
|
"VmType" TEXT,
|
||||||
|
"Datacenter" TEXT,
|
||||||
|
"Cluster" TEXT,
|
||||||
|
"Folder" TEXT,
|
||||||
|
"ProvisionedDisk" REAL,
|
||||||
|
"InitialVcpus" INTEGER,
|
||||||
|
"InitialRam" INTEGER,
|
||||||
|
"IsTemplate" TEXT NOT NULL DEFAULT "FALSE",
|
||||||
|
"PoweredOn" TEXT NOT NULL DEFAULT "FALSE",
|
||||||
|
"SrmPlaceholder" TEXT NOT NULL DEFAULT "FALSE",
|
||||||
|
"VmUuid" TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS updates (
|
||||||
|
"Uid" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"InventoryId" INTEGER,
|
||||||
|
"UpdateTime" INTEGER,
|
||||||
|
"UpdateType" TEXT NOT NULL,
|
||||||
|
"NewVcpus" INTEGER,
|
||||||
|
"NewRam" INTEGER,
|
||||||
|
"NewResourcePool" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"EventId" TEXT,
|
||||||
|
"NewProvisionedDisk" REAL,
|
||||||
|
"UserName" TEXT,
|
||||||
|
"PlaceholderChange" TEXT,
|
||||||
|
"Name" TEXT,
|
||||||
|
"RawChangeString" BLOB
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS events (
|
||||||
|
"Eid" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"CloudId" TEXT NOT NULL,
|
||||||
|
"Source" TEXT NOT NULL,
|
||||||
|
"EventTime" INTEGER,
|
||||||
|
"ChainId" TEXT NOT NULL,
|
||||||
|
"VmId" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"DatacenterName" TEXT,
|
||||||
|
"ComputeResourceName" TEXT,
|
||||||
|
"UserName" TEXT,
|
||||||
|
"Processed" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"DatacenterId" TEXT,
|
||||||
|
"ComputeResourceId" TEXT,
|
||||||
|
"VmName" TEXT,
|
||||||
|
"EventType" TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS inventory_history (
|
||||||
|
"Hid" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"InventoryId" INTEGER,
|
||||||
|
"ReportDate" INTEGER,
|
||||||
|
"UpdateTime" INTEGER,
|
||||||
|
"PreviousVcpus" INTEGER,
|
||||||
|
"PreviousRam" INTEGER,
|
||||||
|
"PreviousResourcePool" TEXT,
|
||||||
|
"PreviousProvisionedDisk" REAL
|
||||||
|
);
|
||||||
293
internal/report/snapshots.go
Normal file
293
internal/report/snapshots.go
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"vctp/db"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListTablesByPrefix(ctx context.Context, database db.Database, prefix string) ([]string, error) {
|
||||||
|
dbConn := database.DB()
|
||||||
|
driver := strings.ToLower(dbConn.DriverName())
|
||||||
|
pattern := prefix + "%"
|
||||||
|
|
||||||
|
var rows *sqlx.Rows
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch driver {
|
||||||
|
case "sqlite":
|
||||||
|
rows, err = dbConn.QueryxContext(ctx, `
|
||||||
|
SELECT name
|
||||||
|
FROM sqlite_master
|
||||||
|
WHERE type = 'table'
|
||||||
|
AND name LIKE ?
|
||||||
|
ORDER BY name DESC
|
||||||
|
`, pattern)
|
||||||
|
case "pgx", "postgres":
|
||||||
|
rows, err = dbConn.QueryxContext(ctx, `
|
||||||
|
SELECT tablename
|
||||||
|
FROM pg_catalog.pg_tables
|
||||||
|
WHERE schemaname = 'public'
|
||||||
|
AND tablename LIKE $1
|
||||||
|
ORDER BY tablename DESC
|
||||||
|
`, pattern)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported driver for listing tables: %s", driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
tables := make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var name string
|
||||||
|
if err := rows.Scan(&name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tables = append(tables, name)
|
||||||
|
}
|
||||||
|
return tables, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatSnapshotLabel(prefix string, tableName string) (string, bool) {
|
||||||
|
if !strings.HasPrefix(tableName, prefix) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
suffix := strings.TrimPrefix(tableName, prefix)
|
||||||
|
switch prefix {
|
||||||
|
case "inventory_daily_":
|
||||||
|
if t, err := time.Parse("20060102", suffix); err == nil {
|
||||||
|
return t.Format("2006-01-02"), true
|
||||||
|
}
|
||||||
|
case "inventory_daily_summary_":
|
||||||
|
if t, err := time.Parse("20060102", suffix); err == nil {
|
||||||
|
return t.Format("2006-01-02"), true
|
||||||
|
}
|
||||||
|
case "inventory_monthly_summary_":
|
||||||
|
if t, err := time.Parse("200601", suffix); err == nil {
|
||||||
|
return t.Format("2006-01"), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTableReport(logger *slog.Logger, Database db.Database, ctx context.Context, tableName string) ([]byte, error) {
|
||||||
|
if err := validateTableName(tableName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbConn := Database.DB()
|
||||||
|
columns, err := tableColumns(ctx, dbConn, tableName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(columns) == 0 {
|
||||||
|
return nil, fmt.Errorf("no columns found for table %s", tableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`SELECT * FROM %s`, tableName)
|
||||||
|
orderBy := snapshotOrderBy(columns)
|
||||||
|
if orderBy != "" {
|
||||||
|
query = fmt.Sprintf(`%s ORDER BY "%s" DESC`, query, orderBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := dbConn.QueryxContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
sheetName := "Snapshot Report"
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
xlsx := excelize.NewFile()
|
||||||
|
if err := xlsx.SetSheetName("Sheet1", sheetName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := xlsx.SetDocProps(&excelize.DocProperties{
|
||||||
|
Creator: "vctp",
|
||||||
|
Created: time.Now().Format(time.RFC3339),
|
||||||
|
}); err != nil {
|
||||||
|
logger.Error("Error setting document properties", "error", err, "sheet_name", sheetName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, columnName := range columns {
|
||||||
|
cell := fmt.Sprintf("%s1", string(rune('A'+i)))
|
||||||
|
xlsx.SetCellValue(sheetName, cell, columnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endCell, err := excelize.CoordinatesToCellName(len(columns), 1); err == nil {
|
||||||
|
filterRange := "A1:" + endCell
|
||||||
|
if err := xlsx.AutoFilter(sheetName, filterRange, nil); err != nil {
|
||||||
|
logger.Error("Error setting autofilter", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerStyle, err := xlsx.NewStyle(&excelize.Style{
|
||||||
|
Font: &excelize.Font{
|
||||||
|
Bold: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
if err := xlsx.SetRowStyle(sheetName, 1, 1, headerStyle); err != nil {
|
||||||
|
logger.Error("Error setting header style", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rowIndex := 2
|
||||||
|
for rows.Next() {
|
||||||
|
values, err := scanRowValues(rows, len(columns))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for colIndex, value := range values {
|
||||||
|
cell := fmt.Sprintf("%s%d", string(rune('A'+colIndex)), rowIndex)
|
||||||
|
xlsx.SetCellValue(sheetName, cell, normalizeCellValue(value))
|
||||||
|
}
|
||||||
|
rowIndex++
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := xlsx.SetPanes(sheetName, &excelize.Panes{
|
||||||
|
Freeze: true,
|
||||||
|
Split: false,
|
||||||
|
XSplit: 0,
|
||||||
|
YSplit: 1,
|
||||||
|
TopLeftCell: "A2",
|
||||||
|
ActivePane: "bottomLeft",
|
||||||
|
Selection: []excelize.Selection{
|
||||||
|
{SQRef: "A2", ActiveCell: "A2", Pane: "bottomLeft"},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
logger.Error("Error freezing top row", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := xlsx.Write(&buffer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTableName(name string) error {
|
||||||
|
if name == "" {
|
||||||
|
return fmt.Errorf("table name is empty")
|
||||||
|
}
|
||||||
|
for _, r := range name {
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid table name: %s", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableColumns(ctx context.Context, dbConn *sqlx.DB, tableName string) ([]string, error) {
|
||||||
|
driver := strings.ToLower(dbConn.DriverName())
|
||||||
|
switch driver {
|
||||||
|
case "sqlite":
|
||||||
|
query := fmt.Sprintf(`PRAGMA table_info("%s")`, tableName)
|
||||||
|
rows, err := dbConn.QueryxContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
columns := make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
cid int
|
||||||
|
name string
|
||||||
|
colType string
|
||||||
|
notNull int
|
||||||
|
defaultVal sql.NullString
|
||||||
|
pk int
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&cid, &name, &colType, ¬Null, &defaultVal, &pk); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
columns = append(columns, name)
|
||||||
|
}
|
||||||
|
return columns, rows.Err()
|
||||||
|
case "pgx", "postgres":
|
||||||
|
rows, err := dbConn.QueryxContext(ctx, `
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = $1
|
||||||
|
ORDER BY ordinal_position
|
||||||
|
`, tableName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
columns := make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var name string
|
||||||
|
if err := rows.Scan(&name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
columns = append(columns, name)
|
||||||
|
}
|
||||||
|
return columns, rows.Err()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported driver for table columns: %s", driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshotOrderBy(columns []string) string {
|
||||||
|
normalized := make(map[string]struct{}, len(columns))
|
||||||
|
for _, col := range columns {
|
||||||
|
normalized[strings.ToLower(col)] = struct{}{}
|
||||||
|
}
|
||||||
|
if _, ok := normalized["snapshottime"]; ok {
|
||||||
|
return "SnapshotTime"
|
||||||
|
}
|
||||||
|
if _, ok := normalized["samplespresent"]; ok {
|
||||||
|
return "SamplesPresent"
|
||||||
|
}
|
||||||
|
if _, ok := normalized["avgispresent"]; ok {
|
||||||
|
return "AvgIsPresent"
|
||||||
|
}
|
||||||
|
if _, ok := normalized["name"]; ok {
|
||||||
|
return "Name"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanRowValues(rows *sqlx.Rows, columnCount int) ([]interface{}, error) {
|
||||||
|
rawValues := make([]interface{}, columnCount)
|
||||||
|
scanArgs := make([]interface{}, columnCount)
|
||||||
|
for i := range rawValues {
|
||||||
|
scanArgs[i] = &rawValues[i]
|
||||||
|
}
|
||||||
|
if err := rows.Scan(scanArgs...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rawValues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeCellValue(value interface{}) interface{} {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case nil:
|
||||||
|
return ""
|
||||||
|
case []byte:
|
||||||
|
return string(v)
|
||||||
|
case time.Time:
|
||||||
|
return v.Format(time.RFC3339)
|
||||||
|
default:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
674
internal/tasks/inventorySnapshots.go
Normal file
674
internal/tasks/inventorySnapshots.go
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
package tasks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"vctp/db/queries"
|
||||||
|
"vctp/internal/report"
|
||||||
|
"vctp/internal/vcenter"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/vmware/govmomi/vim25/mo"
|
||||||
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inventorySnapshotRow struct {
|
||||||
|
InventoryId sql.NullInt64
|
||||||
|
Name string
|
||||||
|
Vcenter string
|
||||||
|
VmId sql.NullString
|
||||||
|
EventKey sql.NullString
|
||||||
|
CloudId sql.NullString
|
||||||
|
CreationTime sql.NullInt64
|
||||||
|
DeletionTime sql.NullInt64
|
||||||
|
ResourcePool sql.NullString
|
||||||
|
VmType sql.NullString
|
||||||
|
Datacenter sql.NullString
|
||||||
|
Cluster sql.NullString
|
||||||
|
Folder sql.NullString
|
||||||
|
ProvisionedDisk sql.NullFloat64
|
||||||
|
InitialVcpus sql.NullInt64
|
||||||
|
InitialRam sql.NullInt64
|
||||||
|
IsTemplate string
|
||||||
|
PoweredOn string
|
||||||
|
SrmPlaceholder string
|
||||||
|
VmUuid sql.NullString
|
||||||
|
SnapshotTime int64
|
||||||
|
IsPresent string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunVcenterSnapshotHourly records hourly inventory snapshots into a daily table.
|
||||||
|
func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Logger) error {
|
||||||
|
startTime := time.Now()
|
||||||
|
tableName, err := dailyInventoryTableName(startTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbConn := c.Database.DB()
|
||||||
|
if err := ensureDailyInventoryTable(ctx, dbConn, tableName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload settings in case vcenter list has changed
|
||||||
|
c.Settings.ReadYMLSettings()
|
||||||
|
|
||||||
|
for _, url := range c.Settings.Values.Settings.VcenterAddresses {
|
||||||
|
c.Logger.Debug("connecting to vcenter for hourly snapshot", "url", url)
|
||||||
|
vc := vcenter.New(c.Logger, c.VcCreds)
|
||||||
|
vc.Login(url)
|
||||||
|
|
||||||
|
vcVms, err := vc.GetAllVmReferences()
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Error("unable to get VMs from vcenter", "error", err, "url", url)
|
||||||
|
vc.Logout()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
inventoryRows, err := c.Database.Queries().GetInventoryByVcenter(ctx, url)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Error("unable to query inventory table", "error", err, "url", url)
|
||||||
|
vc.Logout()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
inventoryByVmID := make(map[string]queries.Inventory, len(inventoryRows))
|
||||||
|
for _, inv := range inventoryRows {
|
||||||
|
if inv.VmId.Valid {
|
||||||
|
inventoryByVmID[inv.VmId.String] = inv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
presentSnapshots := make(map[string]inventorySnapshotRow, len(vcVms))
|
||||||
|
for _, vm := range vcVms {
|
||||||
|
if strings.HasPrefix(vm.Name(), "vCLS-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vmObj, err := vc.ConvertObjToMoVM(vm)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Error("failed to read VM details", "vm_id", vm.Reference().Value, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if vmObj.Config != nil && vmObj.Config.Template {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var inv *queries.Inventory
|
||||||
|
if existing, ok := inventoryByVmID[vm.Reference().Value]; ok {
|
||||||
|
existingCopy := existing
|
||||||
|
inv = &existingCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
row, err := snapshotFromVM(vmObj, vc, startTime, inv)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Error("unable to build snapshot for VM", "vm_id", vm.Reference().Value, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
row.IsPresent = "TRUE"
|
||||||
|
presentSnapshots[vm.Reference().Value] = row
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range presentSnapshots {
|
||||||
|
if err := insertDailyInventoryRow(ctx, dbConn, tableName, row); err != nil {
|
||||||
|
c.Logger.Error("failed to insert hourly snapshot", "error", err, "vm_id", row.VmId.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, inv := range inventoryRows {
|
||||||
|
vmID := inv.VmId.String
|
||||||
|
if vmID != "" {
|
||||||
|
if _, ok := presentSnapshots[vmID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row := snapshotFromInventory(inv, startTime)
|
||||||
|
row.IsPresent = "FALSE"
|
||||||
|
if err := insertDailyInventoryRow(ctx, dbConn, tableName, row); err != nil {
|
||||||
|
c.Logger.Error("failed to insert missing VM snapshot", "error", err, "vm_id", row.VmId.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vc.Logout()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Logger.Debug("Finished hourly vcenter snapshot")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunVcenterDailyAggregate summarizes hourly snapshots into a daily summary table.
|
||||||
|
func (c *CronTask) RunVcenterDailyAggregate(ctx context.Context, logger *slog.Logger) error {
|
||||||
|
targetTime := time.Now().Add(-time.Minute)
|
||||||
|
sourceTable, err := dailyInventoryTableName(targetTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
summaryTable, err := dailySummaryTableName(targetTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbConn := c.Database.DB()
|
||||||
|
if err := ensureDailySummaryTable(ctx, dbConn, summaryTable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
insertQuery := fmt.Sprintf(`
|
||||||
|
INSERT INTO %s (
|
||||||
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
||||||
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
||||||
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid", "SamplesPresent"
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
||||||
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
||||||
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
||||||
|
SUM(CASE WHEN "IsPresent" = 'TRUE' THEN 1 ELSE 0 END) AS "SamplesPresent"
|
||||||
|
FROM %s
|
||||||
|
GROUP BY
|
||||||
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
||||||
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
||||||
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid";
|
||||||
|
`, summaryTable, sourceTable)
|
||||||
|
|
||||||
|
if _, err := dbConn.ExecContext(ctx, insertQuery); err != nil {
|
||||||
|
c.Logger.Error("failed to aggregate daily inventory", "error", err, "source_table", sourceTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Logger.Debug("Finished daily inventory aggregation", "source_table", sourceTable, "summary_table", summaryTable)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunVcenterMonthlyAggregate summarizes the previous month's daily snapshots.
|
||||||
|
func (c *CronTask) RunVcenterMonthlyAggregate(ctx context.Context, logger *slog.Logger) error {
|
||||||
|
now := time.Now()
|
||||||
|
firstOfThisMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||||
|
targetMonth := firstOfThisMonth.AddDate(0, -1, 0)
|
||||||
|
|
||||||
|
monthPrefix := fmt.Sprintf("inventory_daily_%s", targetMonth.Format("200601"))
|
||||||
|
dailyTables, err := report.ListTablesByPrefix(ctx, c.Database, monthPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(dailyTables) == 0 {
|
||||||
|
return fmt.Errorf("no daily snapshot tables found for %s", targetMonth.Format("2006-01"))
|
||||||
|
}
|
||||||
|
|
||||||
|
monthlyTable, err := monthlySummaryTableName(targetMonth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbConn := c.Database.DB()
|
||||||
|
if err := ensureMonthlySummaryTable(ctx, dbConn, monthlyTable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unionQuery := buildUnionQuery(dailyTables, []string{
|
||||||
|
`"InventoryId"`, `"Name"`, `"Vcenter"`, `"VmId"`, `"EventKey"`, `"CloudId"`, `"CreationTime"`,
|
||||||
|
`"DeletionTime"`, `"ResourcePool"`, `"VmType"`, `"Datacenter"`, `"Cluster"`, `"Folder"`,
|
||||||
|
`"ProvisionedDisk"`, `"InitialVcpus"`, `"InitialRam"`, `"IsTemplate"`, `"PoweredOn"`,
|
||||||
|
`"SrmPlaceholder"`, `"VmUuid"`, `"IsPresent"`,
|
||||||
|
})
|
||||||
|
if strings.TrimSpace(unionQuery) == "" {
|
||||||
|
return fmt.Errorf("no valid daily snapshot tables found for %s", targetMonth.Format("2006-01"))
|
||||||
|
}
|
||||||
|
|
||||||
|
insertQuery := fmt.Sprintf(`
|
||||||
|
INSERT INTO %s (
|
||||||
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
||||||
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
||||||
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
||||||
|
"AvgVcpus", "AvgRam", "AvgIsPresent"
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
||||||
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
||||||
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
||||||
|
AVG(CASE WHEN "InitialVcpus" IS NOT NULL THEN "InitialVcpus" END) AS "AvgVcpus",
|
||||||
|
AVG(CASE WHEN "InitialRam" IS NOT NULL THEN "InitialRam" END) AS "AvgRam",
|
||||||
|
AVG(CASE WHEN "IsPresent" = 'TRUE' THEN 1 ELSE 0 END) AS "AvgIsPresent"
|
||||||
|
FROM (
|
||||||
|
%s
|
||||||
|
) snapshots
|
||||||
|
GROUP BY
|
||||||
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
||||||
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
||||||
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid";
|
||||||
|
`, monthlyTable, unionQuery)
|
||||||
|
|
||||||
|
if _, err := dbConn.ExecContext(ctx, insertQuery); err != nil {
|
||||||
|
c.Logger.Error("failed to aggregate monthly inventory", "error", err, "month", targetMonth.Format("2006-01"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Logger.Debug("Finished monthly inventory aggregation", "summary_table", monthlyTable)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunSnapshotCleanup drops hourly and daily snapshot tables older than retention.
|
||||||
|
func (c *CronTask) RunSnapshotCleanup(ctx context.Context, logger *slog.Logger) error {
|
||||||
|
now := time.Now()
|
||||||
|
hourlyMaxDays := getEnvInt("HOURLY_SNAPSHOT_MAX_AGE_DAYS", 60)
|
||||||
|
dailyMaxMonths := getEnvInt("DAILY_SNAPSHOT_MAX_AGE_MONTHS", 12)
|
||||||
|
|
||||||
|
hourlyCutoff := now.AddDate(0, 0, -hourlyMaxDays)
|
||||||
|
dailyCutoff := now.AddDate(0, -dailyMaxMonths, 0)
|
||||||
|
|
||||||
|
dbConn := c.Database.DB()
|
||||||
|
|
||||||
|
hourlyTables, err := report.ListTablesByPrefix(ctx, c.Database, "inventory_daily_")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range hourlyTables {
|
||||||
|
if strings.HasPrefix(table, "inventory_daily_summary_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tableDate, ok := parseSnapshotDate(table, "inventory_daily_", "20060102")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tableDate.Before(truncateDate(hourlyCutoff)) {
|
||||||
|
if err := dropSnapshotTable(ctx, dbConn, table); err != nil {
|
||||||
|
c.Logger.Error("failed to drop hourly snapshot table", "error", err, "table", table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dailyTables, err := report.ListTablesByPrefix(ctx, c.Database, "inventory_daily_summary_")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, table := range dailyTables {
|
||||||
|
tableDate, ok := parseSnapshotDate(table, "inventory_daily_summary_", "20060102")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tableDate.Before(truncateDate(dailyCutoff)) {
|
||||||
|
if err := dropSnapshotTable(ctx, dbConn, table); err != nil {
|
||||||
|
c.Logger.Error("failed to drop daily snapshot table", "error", err, "table", table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Logger.Debug("Finished snapshot cleanup")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dailyInventoryTableName(t time.Time) (string, error) {
|
||||||
|
return safeTableName(fmt.Sprintf("inventory_daily_%s", t.Format("20060102")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func dailySummaryTableName(t time.Time) (string, error) {
|
||||||
|
return safeTableName(fmt.Sprintf("inventory_daily_summary_%s", t.Format("20060102")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func monthlySummaryTableName(t time.Time) (string, error) {
|
||||||
|
return safeTableName(fmt.Sprintf("inventory_monthly_summary_%s", t.Format("200601")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeTableName(name string) (string, error) {
|
||||||
|
for _, r := range name {
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("invalid table name: %s", name)
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDailyInventoryTable(ctx context.Context, dbConn *sqlx.DB, tableName string) error {
|
||||||
|
ddl := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
"InventoryId" BIGINT,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"Vcenter" TEXT NOT NULL,
|
||||||
|
"VmId" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"CloudId" TEXT,
|
||||||
|
"CreationTime" BIGINT,
|
||||||
|
"DeletionTime" BIGINT,
|
||||||
|
"ResourcePool" TEXT,
|
||||||
|
"VmType" TEXT,
|
||||||
|
"Datacenter" TEXT,
|
||||||
|
"Cluster" TEXT,
|
||||||
|
"Folder" TEXT,
|
||||||
|
"ProvisionedDisk" REAL,
|
||||||
|
"InitialVcpus" BIGINT,
|
||||||
|
"InitialRam" BIGINT,
|
||||||
|
"IsTemplate" TEXT,
|
||||||
|
"PoweredOn" TEXT,
|
||||||
|
"SrmPlaceholder" TEXT,
|
||||||
|
"VmUuid" TEXT,
|
||||||
|
"SnapshotTime" BIGINT NOT NULL,
|
||||||
|
"IsPresent" TEXT NOT NULL
|
||||||
|
);`, tableName)
|
||||||
|
|
||||||
|
_, err := dbConn.ExecContext(ctx, ddl)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDailySummaryTable(ctx context.Context, dbConn *sqlx.DB, tableName string) error {
|
||||||
|
ddl := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
"InventoryId" BIGINT,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"Vcenter" TEXT NOT NULL,
|
||||||
|
"VmId" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"CloudId" TEXT,
|
||||||
|
"CreationTime" BIGINT,
|
||||||
|
"DeletionTime" BIGINT,
|
||||||
|
"ResourcePool" TEXT,
|
||||||
|
"VmType" TEXT,
|
||||||
|
"Datacenter" TEXT,
|
||||||
|
"Cluster" TEXT,
|
||||||
|
"Folder" TEXT,
|
||||||
|
"ProvisionedDisk" REAL,
|
||||||
|
"InitialVcpus" BIGINT,
|
||||||
|
"InitialRam" BIGINT,
|
||||||
|
"IsTemplate" TEXT,
|
||||||
|
"PoweredOn" TEXT,
|
||||||
|
"SrmPlaceholder" TEXT,
|
||||||
|
"VmUuid" TEXT,
|
||||||
|
"SamplesPresent" BIGINT NOT NULL
|
||||||
|
);`, tableName)
|
||||||
|
|
||||||
|
_, err := dbConn.ExecContext(ctx, ddl)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureMonthlySummaryTable(ctx context.Context, dbConn *sqlx.DB, tableName string) error {
|
||||||
|
ddl := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
"InventoryId" BIGINT,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"Vcenter" TEXT NOT NULL,
|
||||||
|
"VmId" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"CloudId" TEXT,
|
||||||
|
"CreationTime" BIGINT,
|
||||||
|
"DeletionTime" BIGINT,
|
||||||
|
"ResourcePool" TEXT,
|
||||||
|
"VmType" TEXT,
|
||||||
|
"Datacenter" TEXT,
|
||||||
|
"Cluster" TEXT,
|
||||||
|
"Folder" TEXT,
|
||||||
|
"ProvisionedDisk" REAL,
|
||||||
|
"InitialVcpus" BIGINT,
|
||||||
|
"InitialRam" BIGINT,
|
||||||
|
"IsTemplate" TEXT,
|
||||||
|
"PoweredOn" TEXT,
|
||||||
|
"SrmPlaceholder" TEXT,
|
||||||
|
"VmUuid" TEXT,
|
||||||
|
"AvgVcpus" REAL,
|
||||||
|
"AvgRam" REAL,
|
||||||
|
"AvgIsPresent" REAL
|
||||||
|
);`, tableName)
|
||||||
|
|
||||||
|
_, err := dbConn.ExecContext(ctx, ddl)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildUnionQuery(tables []string, columns []string) string {
|
||||||
|
queries := make([]string, 0, len(tables))
|
||||||
|
columnList := strings.Join(columns, ", ")
|
||||||
|
for _, table := range tables {
|
||||||
|
if _, err := safeTableName(table); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
queries = append(queries, fmt.Sprintf("SELECT %s FROM %s", columnList, table))
|
||||||
|
}
|
||||||
|
return strings.Join(queries, "\nUNION ALL\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSnapshotDate(table string, prefix string, layout string) (time.Time, bool) {
|
||||||
|
if !strings.HasPrefix(table, prefix) {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
suffix := strings.TrimPrefix(table, prefix)
|
||||||
|
parsed, err := time.Parse(layout, suffix)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
return parsed, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncateDate(t time.Time) time.Time {
|
||||||
|
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropSnapshotTable(ctx context.Context, dbConn *sqlx.DB, table string) error {
|
||||||
|
if _, err := safeTableName(table); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := dbConn.ExecContext(ctx, fmt.Sprintf("DROP TABLE IF EXISTS %s", table))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvInt(key string, fallback int) int {
|
||||||
|
raw := strings.TrimSpace(os.Getenv(key))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
value, err := strconv.Atoi(raw)
|
||||||
|
if err != nil || value < 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshotFromVM(vmObject *mo.VirtualMachine, vc *vcenter.Vcenter, snapshotTime time.Time, inv *queries.Inventory) (inventorySnapshotRow, error) {
|
||||||
|
if vmObject == nil {
|
||||||
|
return inventorySnapshotRow{}, fmt.Errorf("missing VM object")
|
||||||
|
}
|
||||||
|
|
||||||
|
row := inventorySnapshotRow{
|
||||||
|
Name: vmObject.Name,
|
||||||
|
Vcenter: vc.Vurl,
|
||||||
|
VmId: sql.NullString{String: vmObject.Reference().Value, Valid: vmObject.Reference().Value != ""},
|
||||||
|
SnapshotTime: snapshotTime.Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if inv != nil {
|
||||||
|
row.InventoryId = sql.NullInt64{Int64: inv.Iid, Valid: inv.Iid > 0}
|
||||||
|
row.EventKey = inv.EventKey
|
||||||
|
row.CloudId = inv.CloudId
|
||||||
|
row.DeletionTime = inv.DeletionTime
|
||||||
|
row.VmType = inv.VmType
|
||||||
|
}
|
||||||
|
|
||||||
|
if vmObject.Config != nil {
|
||||||
|
row.VmUuid = sql.NullString{String: vmObject.Config.Uuid, Valid: vmObject.Config.Uuid != ""}
|
||||||
|
if !vmObject.Config.CreateDate.IsZero() {
|
||||||
|
row.CreationTime = sql.NullInt64{Int64: vmObject.Config.CreateDate.Unix(), Valid: true}
|
||||||
|
}
|
||||||
|
row.InitialVcpus = sql.NullInt64{Int64: int64(vmObject.Config.Hardware.NumCPU), Valid: vmObject.Config.Hardware.NumCPU > 0}
|
||||||
|
row.InitialRam = sql.NullInt64{Int64: int64(vmObject.Config.Hardware.MemoryMB), Valid: vmObject.Config.Hardware.MemoryMB > 0}
|
||||||
|
|
||||||
|
totalDiskBytes := int64(0)
|
||||||
|
for _, device := range vmObject.Config.Hardware.Device {
|
||||||
|
if disk, ok := device.(*types.VirtualDisk); ok {
|
||||||
|
totalDiskBytes += disk.CapacityInBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if totalDiskBytes > 0 {
|
||||||
|
row.ProvisionedDisk = sql.NullFloat64{Float64: float64(totalDiskBytes / 1024 / 1024 / 1024), Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" && vmObject.Config.ManagedBy.Type == "placeholderVm" {
|
||||||
|
row.SrmPlaceholder = "TRUE"
|
||||||
|
} else {
|
||||||
|
row.SrmPlaceholder = "FALSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if vmObject.Config.Template {
|
||||||
|
row.IsTemplate = "TRUE"
|
||||||
|
} else {
|
||||||
|
row.IsTemplate = "FALSE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vmObject.Runtime.PowerState == "poweredOff" {
|
||||||
|
row.PoweredOn = "FALSE"
|
||||||
|
} else {
|
||||||
|
row.PoweredOn = "TRUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if inv != nil {
|
||||||
|
row.ResourcePool = inv.ResourcePool
|
||||||
|
row.Datacenter = inv.Datacenter
|
||||||
|
row.Cluster = inv.Cluster
|
||||||
|
row.Folder = inv.Folder
|
||||||
|
if !row.CreationTime.Valid {
|
||||||
|
row.CreationTime = inv.CreationTime
|
||||||
|
}
|
||||||
|
if !row.ProvisionedDisk.Valid {
|
||||||
|
row.ProvisionedDisk = inv.ProvisionedDisk
|
||||||
|
}
|
||||||
|
if !row.InitialVcpus.Valid {
|
||||||
|
row.InitialVcpus = inv.InitialVcpus
|
||||||
|
}
|
||||||
|
if !row.InitialRam.Valid {
|
||||||
|
row.InitialRam = inv.InitialRam
|
||||||
|
}
|
||||||
|
if row.IsTemplate == "" {
|
||||||
|
row.IsTemplate = boolStringFromInterface(inv.IsTemplate)
|
||||||
|
}
|
||||||
|
if row.PoweredOn == "" {
|
||||||
|
row.PoweredOn = boolStringFromInterface(inv.PoweredOn)
|
||||||
|
}
|
||||||
|
if row.SrmPlaceholder == "" {
|
||||||
|
row.SrmPlaceholder = boolStringFromInterface(inv.SrmPlaceholder)
|
||||||
|
}
|
||||||
|
if !row.VmUuid.Valid {
|
||||||
|
row.VmUuid = inv.VmUuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.ResourcePool.String == "" {
|
||||||
|
if rpName, err := vc.GetVmResourcePool(*vmObject); err == nil {
|
||||||
|
row.ResourcePool = sql.NullString{String: rpName, Valid: rpName != ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.Folder.String == "" {
|
||||||
|
if folderPath, err := vc.GetVMFolderPath(*vmObject); err == nil {
|
||||||
|
row.Folder = sql.NullString{String: folderPath, Valid: folderPath != ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.Cluster.String == "" {
|
||||||
|
if clusterName, err := vc.GetClusterFromHost(vmObject.Runtime.Host); err == nil {
|
||||||
|
row.Cluster = sql.NullString{String: clusterName, Valid: clusterName != ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.Datacenter.String == "" {
|
||||||
|
if dcName, err := vc.GetDatacenterForVM(*vmObject); err == nil {
|
||||||
|
row.Datacenter = sql.NullString{String: dcName, Valid: dcName != ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return row, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshotFromInventory(inv queries.Inventory, snapshotTime time.Time) inventorySnapshotRow {
|
||||||
|
return inventorySnapshotRow{
|
||||||
|
InventoryId: sql.NullInt64{Int64: inv.Iid, Valid: inv.Iid > 0},
|
||||||
|
Name: inv.Name,
|
||||||
|
Vcenter: inv.Vcenter,
|
||||||
|
VmId: inv.VmId,
|
||||||
|
EventKey: inv.EventKey,
|
||||||
|
CloudId: inv.CloudId,
|
||||||
|
CreationTime: inv.CreationTime,
|
||||||
|
DeletionTime: inv.DeletionTime,
|
||||||
|
ResourcePool: inv.ResourcePool,
|
||||||
|
VmType: inv.VmType,
|
||||||
|
Datacenter: inv.Datacenter,
|
||||||
|
Cluster: inv.Cluster,
|
||||||
|
Folder: inv.Folder,
|
||||||
|
ProvisionedDisk: inv.ProvisionedDisk,
|
||||||
|
InitialVcpus: inv.InitialVcpus,
|
||||||
|
InitialRam: inv.InitialRam,
|
||||||
|
IsTemplate: boolStringFromInterface(inv.IsTemplate),
|
||||||
|
PoweredOn: boolStringFromInterface(inv.PoweredOn),
|
||||||
|
SrmPlaceholder: boolStringFromInterface(inv.SrmPlaceholder),
|
||||||
|
VmUuid: inv.VmUuid,
|
||||||
|
SnapshotTime: snapshotTime.Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertDailyInventoryRow(ctx context.Context, dbConn *sqlx.DB, tableName string, row inventorySnapshotRow) error {
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
INSERT INTO %s (
|
||||||
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
||||||
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
||||||
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid", "SnapshotTime", "IsPresent"
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
|
`, tableName)
|
||||||
|
|
||||||
|
query = sqlx.Rebind(sqlx.BindType(dbConn.DriverName()), query)
|
||||||
|
|
||||||
|
_, err := dbConn.ExecContext(ctx, query,
|
||||||
|
row.InventoryId,
|
||||||
|
row.Name,
|
||||||
|
row.Vcenter,
|
||||||
|
row.VmId,
|
||||||
|
row.EventKey,
|
||||||
|
row.CloudId,
|
||||||
|
row.CreationTime,
|
||||||
|
row.DeletionTime,
|
||||||
|
row.ResourcePool,
|
||||||
|
row.VmType,
|
||||||
|
row.Datacenter,
|
||||||
|
row.Cluster,
|
||||||
|
row.Folder,
|
||||||
|
row.ProvisionedDisk,
|
||||||
|
row.InitialVcpus,
|
||||||
|
row.InitialRam,
|
||||||
|
row.IsTemplate,
|
||||||
|
row.PoweredOn,
|
||||||
|
row.SrmPlaceholder,
|
||||||
|
row.VmUuid,
|
||||||
|
row.SnapshotTime,
|
||||||
|
row.IsPresent,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolStringFromInterface(value interface{}) string {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case nil:
|
||||||
|
return ""
|
||||||
|
case string:
|
||||||
|
return v
|
||||||
|
case []byte:
|
||||||
|
return string(v)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
|
return "FALSE"
|
||||||
|
case int:
|
||||||
|
if v != 0 {
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
|
return "FALSE"
|
||||||
|
case int64:
|
||||||
|
if v != 0 {
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
|
return "FALSE"
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
97
main.go
97
main.go
@@ -23,12 +23,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bindDisableTls bool
|
bindDisableTls bool
|
||||||
sha1ver string // sha1 revision used to build the program
|
sha1ver string // sha1 revision used to build the program
|
||||||
buildTime string // when the executable was built
|
buildTime string // when the executable was built
|
||||||
cronFrequency time.Duration
|
cronFrequency time.Duration
|
||||||
cronInvFrequency time.Duration
|
cronInvFrequency time.Duration
|
||||||
encryptionKey = []byte("5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa")
|
cronSnapshotFrequency time.Duration
|
||||||
|
cronAggregateFrequency time.Duration
|
||||||
|
encryptionKey = []byte("5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -189,6 +191,30 @@ func main() {
|
|||||||
}
|
}
|
||||||
logger.Debug("Setting VM inventory polling cronjob frequency to", "frequency", cronInvFrequency)
|
logger.Debug("Setting VM inventory polling cronjob frequency to", "frequency", cronInvFrequency)
|
||||||
|
|
||||||
|
cronSnapshotFrequencyString := os.Getenv("VCENTER_INVENTORY_SNAPSHOT_SECONDS")
|
||||||
|
if cronSnapshotFrequencyString != "" {
|
||||||
|
cronSnapshotFrequency, err = time.ParseDuration(cronSnapshotFrequencyString)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Can't convert VCENTER_INVENTORY_SNAPSHOT_SECONDS value to time duration. Defaulting to 3600", "value", cronSnapshotFrequencyString, "error", err)
|
||||||
|
cronSnapshotFrequency = time.Hour
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cronSnapshotFrequency = time.Hour
|
||||||
|
}
|
||||||
|
logger.Debug("Setting VM inventory snapshot cronjob frequency to", "frequency", cronSnapshotFrequency)
|
||||||
|
|
||||||
|
cronAggregateFrequencyString := os.Getenv("VCENTER_INVENTORY_AGGREGATE_SECONDS")
|
||||||
|
if cronAggregateFrequencyString != "" {
|
||||||
|
cronAggregateFrequency, err = time.ParseDuration(cronAggregateFrequencyString)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Can't convert VCENTER_INVENTORY_AGGREGATE_SECONDS value to time duration. Defaulting to 86400", "value", cronAggregateFrequencyString, "error", err)
|
||||||
|
cronAggregateFrequency = time.Hour * 24
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cronAggregateFrequency = time.Hour * 24
|
||||||
|
}
|
||||||
|
logger.Debug("Setting VM inventory aggregation cronjob frequency to", "frequency", cronAggregateFrequency)
|
||||||
|
|
||||||
// start background processing for events stored in events table
|
// start background processing for events stored in events table
|
||||||
startsAt := time.Now().Add(time.Second * 10)
|
startsAt := time.Now().Add(time.Second * 10)
|
||||||
job, err := c.NewJob(
|
job, err := c.NewJob(
|
||||||
@@ -219,6 +245,65 @@ func main() {
|
|||||||
}
|
}
|
||||||
logger.Debug("Created vcenter inventory cron job", "job", job2.ID(), "starting_at", startsAt2)
|
logger.Debug("Created vcenter inventory cron job", "job", job2.ID(), "starting_at", startsAt2)
|
||||||
|
|
||||||
|
startsAt3 := time.Now().Add(cronSnapshotFrequency)
|
||||||
|
if cronSnapshotFrequency == time.Hour {
|
||||||
|
startsAt3 = time.Now().Truncate(time.Hour).Add(time.Hour)
|
||||||
|
}
|
||||||
|
job3, err := c.NewJob(
|
||||||
|
gocron.DurationJob(cronSnapshotFrequency),
|
||||||
|
gocron.NewTask(func() {
|
||||||
|
ct.RunVcenterSnapshotHourly(ctx, logger)
|
||||||
|
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
gocron.WithStartAt(gocron.WithStartDateTime(startsAt3)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to start vcenter inventory snapshot cron job", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
logger.Debug("Created vcenter inventory snapshot cron job", "job", job3.ID(), "starting_at", startsAt3)
|
||||||
|
|
||||||
|
startsAt4 := time.Now().Add(cronAggregateFrequency)
|
||||||
|
if cronAggregateFrequency == time.Hour*24 {
|
||||||
|
now := time.Now()
|
||||||
|
startsAt4 = time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
|
||||||
|
}
|
||||||
|
job4, err := c.NewJob(
|
||||||
|
gocron.DurationJob(cronAggregateFrequency),
|
||||||
|
gocron.NewTask(func() {
|
||||||
|
ct.RunVcenterDailyAggregate(ctx, logger)
|
||||||
|
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
gocron.WithStartAt(gocron.WithStartDateTime(startsAt4)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to start vcenter inventory aggregation cron job", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
logger.Debug("Created vcenter inventory aggregation cron job", "job", job4.ID(), "starting_at", startsAt4)
|
||||||
|
|
||||||
|
job5, err := c.NewJob(
|
||||||
|
gocron.CronJob("0 0 1 * *", false),
|
||||||
|
gocron.NewTask(func() {
|
||||||
|
ct.RunVcenterMonthlyAggregate(ctx, logger)
|
||||||
|
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to start vcenter monthly aggregation cron job", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
logger.Debug("Created vcenter monthly aggregation cron job", "job", job5.ID())
|
||||||
|
|
||||||
|
job6, err := c.NewJob(
|
||||||
|
gocron.CronJob("0 30 2 * *", false),
|
||||||
|
gocron.NewTask(func() {
|
||||||
|
ct.RunSnapshotCleanup(ctx, logger)
|
||||||
|
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to start snapshot cleanup cron job", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
logger.Debug("Created snapshot cleanup cron job", "job", job6.ID())
|
||||||
|
|
||||||
// start cron scheduler
|
// start cron scheduler
|
||||||
c.Start()
|
c.Start()
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,19 @@
|
|||||||
# disable CGO for cross-compiling
|
# disable CGO for cross-compiling
|
||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
commit=$(git rev-parse HEAD)
|
|
||||||
#tag=$(git describe --tags --abbrev=0)
|
|
||||||
buildtime=$(TZ=Australia/Sydney date +%Y-%m-%dT%T%z)
|
|
||||||
git_version=$(git describe --tags --always --long --dirty)
|
|
||||||
package_name=vctp
|
package_name=vctp
|
||||||
|
commit=$(git rev-parse HEAD)
|
||||||
|
buildtime=$(date +%Y-%m-%dT%T%z)
|
||||||
|
#Extract the version from yml
|
||||||
|
package_version=$(grep 'version:' "$package_name.yml" | awk '{print $2}' | tr -d '"' | sed 's/^v//')
|
||||||
|
|
||||||
#platforms=("linux/amd64" "darwin/amd64")
|
#platforms=("linux/amd64" "darwin/amd64")
|
||||||
platforms=("linux/amd64")
|
platforms=("linux/amd64")
|
||||||
|
|
||||||
echo Building $package_name with git version: $git_version
|
echo Building::
|
||||||
|
echo - Version $package_version
|
||||||
|
echo - Commit $commit
|
||||||
|
echo - Build Time $buildtime
|
||||||
for platform in "${platforms[@]}"
|
for platform in "${platforms[@]}"
|
||||||
do
|
do
|
||||||
platform_split=(${platform//\// })
|
platform_split=(${platform//\// })
|
||||||
@@ -25,14 +28,16 @@ do
|
|||||||
|
|
||||||
starttime=$(TZ=Australia/Sydney date +%Y-%m-%dT%T%z)
|
starttime=$(TZ=Australia/Sydney date +%Y-%m-%dT%T%z)
|
||||||
echo "build commences at $starttime"
|
echo "build commences at $starttime"
|
||||||
env GOOS=$GOOS GOARCH=$GOARCH go build -trimpath -ldflags="-X main.sha1ver=$commit -X main.buildTime=$buildtime" -o build/$output_name $package
|
env GOOS=$GOOS GOARCH=$GOARCH go build -trimpath -ldflags="-X main.version=$package_version -X main.commit=$commit -X main.buildTime=$buildtime" -o build/$output_name $package
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo 'An error has occurred! Aborting the script execution...'
|
echo 'An error has occurred! Aborting the script execution...'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
gzip build/$output_name
|
#gzip build/$output_name
|
||||||
echo "build complete at $buildtime : $output_name"
|
echo "build complete at $buildtime : $output_name"
|
||||||
sha256sum build/${output_name}.gz > build/${output_name}_checksum.txt
|
#sha256sum build/${output_name}.gz > build/${output_name}_checksum.txt
|
||||||
done
|
done
|
||||||
|
|
||||||
|
#nfpm package --config $package_name.yml --packager rpm --target build/
|
||||||
|
|
||||||
ls -lah build
|
ls -lah build
|
||||||
51
scripts/update-swagger-ui.sh
Executable file
51
scripts/update-swagger-ui.sh
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Usage: ./update-swagger-ui.sh [version]
|
||||||
|
# Example: ./update-swagger-ui.sh v5.17.14
|
||||||
|
# If no version is provided, defaults below is used.
|
||||||
|
VERSION="${1:-v5.29.5}"
|
||||||
|
|
||||||
|
TARGET_DIR="server/router/swagger-ui-dist"
|
||||||
|
TARBALL_URL="https://github.com/swagger-api/swagger-ui/archive/refs/tags/${VERSION}.tar.gz"
|
||||||
|
|
||||||
|
echo ">> Fetching Swagger UI ${VERSION} …"
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
cleanup() { rm -rf "$tmpdir"; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Requirements check
|
||||||
|
for cmd in curl tar; do
|
||||||
|
command -v "$cmd" >/dev/null 2>&1 || { echo "ERROR: $cmd not found"; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
# Download & unpack
|
||||||
|
curl -fsSL "$TARBALL_URL" | tar -xz -C "$tmpdir"
|
||||||
|
SRC_DIR="${tmpdir}/swagger-ui-${VERSION#v}/dist"
|
||||||
|
if [[ ! -d "$SRC_DIR" ]]; then
|
||||||
|
echo "ERROR: Unpacked dist not found at $SRC_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace target
|
||||||
|
rm -rf "$TARGET_DIR"
|
||||||
|
mkdir -p "$TARGET_DIR"
|
||||||
|
# Use cp -a for portability (avoids rsync dependency)
|
||||||
|
cp -a "${SRC_DIR}/." "$TARGET_DIR/"
|
||||||
|
|
||||||
|
INDEX="${TARGET_DIR}/swagger-initializer.js"
|
||||||
|
if [[ ! -f "$INDEX" ]]; then
|
||||||
|
echo "ERROR: ${INDEX} not found after copy"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ">> Patching swagger-initializer.js to point at /swagger.json"
|
||||||
|
|
||||||
|
sed -i -E \
|
||||||
|
-e 's#configUrl:[[:space:]]*["'\''"][^"'\''"]*["'\''"]#url: "/swagger.json"#' \
|
||||||
|
-e 's#url:[[:space:]]*["'\''"][^"'\''"]*["'\''"]#url: "/swagger.json"#' \
|
||||||
|
-e 's#urls:[[:space:]]*\[[^]]*\]#url: "/swagger.json"#' \
|
||||||
|
-e '/url:[[:space:]]*"[^\"]*swagger\.json"[[:space:]]*,?$/a\ validatorUrl: null,' \
|
||||||
|
"$INDEX"
|
||||||
|
|
||||||
|
echo ">> Done. Files are in ${TARGET_DIR}"
|
||||||
124
server/handler/snapshots.go
Normal file
124
server/handler/snapshots.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"vctp/components/views"
|
||||||
|
"vctp/internal/report"
|
||||||
|
|
||||||
|
"github.com/a-h/templ"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SnapshotHourlyList renders the hourly snapshot list page.
|
||||||
|
// @Summary List hourly snapshots
|
||||||
|
// @Description Lists hourly inventory snapshot tables.
|
||||||
|
// @Tags snapshots
|
||||||
|
// @Produce text/html
|
||||||
|
// @Success 200 {string} string "HTML page"
|
||||||
|
// @Failure 500 {string} string "Server error"
|
||||||
|
// @Router /snapshots/hourly [get]
|
||||||
|
func (h *Handler) SnapshotHourlyList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.renderSnapshotList(w, r, "inventory_daily_", "Hourly Inventory Snapshots", views.SnapshotHourlyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotDailyList renders the daily snapshot list page.
|
||||||
|
// @Summary List daily snapshots
|
||||||
|
// @Description Lists daily summary snapshot tables.
|
||||||
|
// @Tags snapshots
|
||||||
|
// @Produce text/html
|
||||||
|
// @Success 200 {string} string "HTML page"
|
||||||
|
// @Failure 500 {string} string "Server error"
|
||||||
|
// @Router /snapshots/daily [get]
|
||||||
|
func (h *Handler) SnapshotDailyList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.renderSnapshotList(w, r, "inventory_daily_summary_", "Daily Inventory Snapshots", views.SnapshotDailyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotMonthlyList renders the monthly snapshot list page.
|
||||||
|
// @Summary List monthly snapshots
|
||||||
|
// @Description Lists monthly summary snapshot tables.
|
||||||
|
// @Tags snapshots
|
||||||
|
// @Produce text/html
|
||||||
|
// @Success 200 {string} string "HTML page"
|
||||||
|
// @Failure 500 {string} string "Server error"
|
||||||
|
// @Router /snapshots/monthly [get]
|
||||||
|
func (h *Handler) SnapshotMonthlyList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.renderSnapshotList(w, r, "inventory_monthly_summary_", "Monthly Inventory Snapshots", views.SnapshotMonthlyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotReportDownload streams a snapshot table as XLSX.
|
||||||
|
// @Summary Download snapshot report
|
||||||
|
// @Description Downloads a snapshot table as an XLSX file.
|
||||||
|
// @Tags snapshots
|
||||||
|
// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
// @Param table query string true "Snapshot table name"
|
||||||
|
// @Success 200 {file} file "Snapshot XLSX report"
|
||||||
|
// @Failure 400 {object} map[string]string "Invalid request"
|
||||||
|
// @Failure 500 {object} map[string]string "Server error"
|
||||||
|
// @Router /api/report/snapshot [get]
|
||||||
|
func (h *Handler) SnapshotReportDownload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.Background()
|
||||||
|
tableName := r.URL.Query().Get("table")
|
||||||
|
if tableName == "" {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"status": "ERROR",
|
||||||
|
"message": "Missing table parameter",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reportData, err := report.CreateTableReport(h.Logger, h.Database, ctx, tableName)
|
||||||
|
if err != nil {
|
||||||
|
h.Logger.Error("Failed to create snapshot report", "error", err, "table", tableName)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"status": "ERROR",
|
||||||
|
"message": fmt.Sprintf("Unable to create snapshot report: '%s'", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%s.xlsx", tableName)
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||||
|
w.Header().Set("File-Name", filename)
|
||||||
|
w.Write(reportData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) renderSnapshotList(w http.ResponseWriter, r *http.Request, prefix string, title string, renderer func([]views.SnapshotEntry) templ.Component) {
|
||||||
|
ctx := context.Background()
|
||||||
|
tables, err := report.ListTablesByPrefix(ctx, h.Database, prefix)
|
||||||
|
if err != nil {
|
||||||
|
h.Logger.Error("Failed to list snapshot tables", "error", err, "prefix", prefix)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "Unable to list snapshot tables: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]views.SnapshotEntry, 0, len(tables))
|
||||||
|
for _, table := range tables {
|
||||||
|
if prefix == "inventory_daily_" && strings.HasPrefix(table, "inventory_daily_summary_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
label := table
|
||||||
|
if parsed, ok := report.FormatSnapshotLabel(prefix, table); ok {
|
||||||
|
label = parsed
|
||||||
|
}
|
||||||
|
entries = append(entries, views.SnapshotEntry{
|
||||||
|
Label: label,
|
||||||
|
Link: "/api/report/snapshot?table=" + url.QueryEscape(table),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := renderer(entries).Render(r.Context(), w); err != nil {
|
||||||
|
h.Logger.Error("Failed to render snapshot list", "error", err, "title", title)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "Failed to render snapshot list")
|
||||||
|
}
|
||||||
|
}
|
||||||
1
server/router/.gitignore
vendored
Normal file
1
server/router/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
swagger-ui-dist/
|
||||||
768
server/router/docs/docs.go
Normal file
768
server/router/docs/docs.go
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
package docs
|
||||||
|
|
||||||
|
import "github.com/swaggo/swag"
|
||||||
|
|
||||||
|
const docTemplate = `{
|
||||||
|
"schemes": {{ marshal .Schemes }},
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "{{escape .Description}}",
|
||||||
|
"title": "{{.Title}}",
|
||||||
|
"contact": {},
|
||||||
|
"version": "{{.Version}}"
|
||||||
|
},
|
||||||
|
"host": "{{.Host}}",
|
||||||
|
"basePath": "{{.BasePath}}",
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"description": "Renders the main UI page.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ui"
|
||||||
|
],
|
||||||
|
"summary": "Home page",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Render failed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/cleanup/updates": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Removes update records that are no longer associated with a VM.",
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"maintenance"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup updates",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Cleanup completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/cleanup/vcenter": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Removes all inventory entries associated with a vCenter URL.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"maintenance"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup vCenter inventory",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "vCenter URL",
|
||||||
|
"name": "vc_url",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Cleanup completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/encrypt": {
|
||||||
|
"post": {
|
||||||
|
"description": "Encrypts a plaintext value and returns the ciphertext.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crypto"
|
||||||
|
],
|
||||||
|
"summary": "Encrypt data",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Plaintext payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Ciphertext response",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/create": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM create CloudEvent and stores the event data.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM create event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Create event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/delete": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM delete CloudEvent and marks the VM as deleted in inventory.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM delete event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Delete event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/modify": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM modify CloudEvent and creates an update record when relevant changes are detected.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM modify event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Modify event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"202": {
|
||||||
|
"description": "No relevant changes",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/move": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM move CloudEvent and creates an update record.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM move event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Move event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/import/vm": {
|
||||||
|
"post": {
|
||||||
|
"description": "Imports existing VM inventory data in bulk.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"summary": "Import VMs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Bulk import payload",
|
||||||
|
"name": "import",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.ImportReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Import processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/inventory/vm/delete": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Removes a VM inventory entry by VM ID and datacenter name.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup VM inventory entry",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "VM ID",
|
||||||
|
"name": "vm_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Datacenter name",
|
||||||
|
"name": "datacenter_name",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Cleanup completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/inventory/vm/update": {
|
||||||
|
"post": {
|
||||||
|
"description": "Queries vCenter and updates inventory records with missing details.",
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"summary": "Refresh VM details",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Update completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/report/inventory": {
|
||||||
|
"get": {
|
||||||
|
"description": "Generates an inventory XLSX report and returns it as a file download.",
|
||||||
|
"produces": [
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"reports"
|
||||||
|
],
|
||||||
|
"summary": "Download inventory report",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Inventory XLSX report",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Report generation failed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/report/snapshot": {
|
||||||
|
"get": {
|
||||||
|
"description": "Downloads a snapshot table as an XLSX file.",
|
||||||
|
"produces": [
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "Download snapshot report",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Snapshot table name",
|
||||||
|
"name": "table",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Snapshot XLSX report",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/report/updates": {
|
||||||
|
"get": {
|
||||||
|
"description": "Generates an updates XLSX report and returns it as a file download.",
|
||||||
|
"produces": [
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"reports"
|
||||||
|
],
|
||||||
|
"summary": "Download updates report",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Updates XLSX report",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Report generation failed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/snapshots/daily": {
|
||||||
|
"get": {
|
||||||
|
"description": "Lists daily summary snapshot tables.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "List daily snapshots",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/snapshots/hourly": {
|
||||||
|
"get": {
|
||||||
|
"description": "Lists hourly inventory snapshot tables.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "List hourly snapshots",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/snapshots/monthly": {
|
||||||
|
"get": {
|
||||||
|
"description": "Lists monthly summary snapshot tables.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "List monthly snapshots",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"models.CloudEventReceived": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"models.CloudEventResourcePool": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ResourcePool": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.CloudEventVm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Vm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.ImportReceived": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Cluster": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"CreationTime": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"Datacenter": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Folder": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"InitialRam": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"InitialVcpus": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"PowerState": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"ProvisionedDisk": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"ResourcePool": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Vcenter": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"VmId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
var SwaggerInfo = &swag.Spec{
|
||||||
|
Version: "",
|
||||||
|
Host: "",
|
||||||
|
BasePath: "",
|
||||||
|
Schemes: []string{},
|
||||||
|
Title: "",
|
||||||
|
Description: "",
|
||||||
|
InfoInstanceName: "swagger",
|
||||||
|
SwaggerTemplate: docTemplate,
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
|
}
|
||||||
739
server/router/docs/swagger.json
Normal file
739
server/router/docs/swagger.json
Normal file
@@ -0,0 +1,739 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"contact": {}
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"description": "Renders the main UI page.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ui"
|
||||||
|
],
|
||||||
|
"summary": "Home page",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Render failed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/cleanup/updates": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Removes update records that are no longer associated with a VM.",
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"maintenance"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup updates",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Cleanup completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/cleanup/vcenter": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Removes all inventory entries associated with a vCenter URL.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"maintenance"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup vCenter inventory",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "vCenter URL",
|
||||||
|
"name": "vc_url",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Cleanup completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/encrypt": {
|
||||||
|
"post": {
|
||||||
|
"description": "Encrypts a plaintext value and returns the ciphertext.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crypto"
|
||||||
|
],
|
||||||
|
"summary": "Encrypt data",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Plaintext payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Ciphertext response",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/create": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM create CloudEvent and stores the event data.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM create event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Create event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/delete": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM delete CloudEvent and marks the VM as deleted in inventory.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM delete event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Delete event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/modify": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM modify CloudEvent and creates an update record when relevant changes are detected.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM modify event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Modify event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"202": {
|
||||||
|
"description": "No relevant changes",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/event/vm/move": {
|
||||||
|
"post": {
|
||||||
|
"description": "Parses a VM move CloudEvent and creates an update record.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"events"
|
||||||
|
],
|
||||||
|
"summary": "Record VM move event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "CloudEvent payload",
|
||||||
|
"name": "event",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CloudEventReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Move event processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/import/vm": {
|
||||||
|
"post": {
|
||||||
|
"description": "Imports existing VM inventory data in bulk.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"summary": "Import VMs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Bulk import payload",
|
||||||
|
"name": "import",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.ImportReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Import processed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/inventory/vm/delete": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Removes a VM inventory entry by VM ID and datacenter name.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup VM inventory entry",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "VM ID",
|
||||||
|
"name": "vm_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Datacenter name",
|
||||||
|
"name": "datacenter_name",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Cleanup completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/inventory/vm/update": {
|
||||||
|
"post": {
|
||||||
|
"description": "Queries vCenter and updates inventory records with missing details.",
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"summary": "Refresh VM details",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Update completed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/report/inventory": {
|
||||||
|
"get": {
|
||||||
|
"description": "Generates an inventory XLSX report and returns it as a file download.",
|
||||||
|
"produces": [
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"reports"
|
||||||
|
],
|
||||||
|
"summary": "Download inventory report",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Inventory XLSX report",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Report generation failed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/report/snapshot": {
|
||||||
|
"get": {
|
||||||
|
"description": "Downloads a snapshot table as an XLSX file.",
|
||||||
|
"produces": [
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "Download snapshot report",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Snapshot table name",
|
||||||
|
"name": "table",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Snapshot XLSX report",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/report/updates": {
|
||||||
|
"get": {
|
||||||
|
"description": "Generates an updates XLSX report and returns it as a file download.",
|
||||||
|
"produces": [
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"reports"
|
||||||
|
],
|
||||||
|
"summary": "Download updates report",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Updates XLSX report",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Report generation failed",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/snapshots/daily": {
|
||||||
|
"get": {
|
||||||
|
"description": "Lists daily summary snapshot tables.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "List daily snapshots",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/snapshots/hourly": {
|
||||||
|
"get": {
|
||||||
|
"description": "Lists hourly inventory snapshot tables.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "List hourly snapshots",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/snapshots/monthly": {
|
||||||
|
"get": {
|
||||||
|
"description": "Lists monthly summary snapshot tables.",
|
||||||
|
"produces": [
|
||||||
|
"text/html"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
"summary": "List monthly snapshots",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "HTML page",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"models.CloudEventReceived": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"models.CloudEventResourcePool": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ResourcePool": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.CloudEventVm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Vm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.ImportReceived": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Cluster": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"CreationTime": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"Datacenter": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Folder": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"InitialRam": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"InitialVcpus": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"PowerState": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"ProvisionedDisk": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"ResourcePool": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Vcenter": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"VmId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
483
server/router/docs/swagger.yaml
Normal file
483
server/router/docs/swagger.yaml
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
definitions:
|
||||||
|
models.CloudEventReceived:
|
||||||
|
type: object
|
||||||
|
models.CloudEventResourcePool:
|
||||||
|
properties:
|
||||||
|
Name:
|
||||||
|
type: string
|
||||||
|
ResourcePool:
|
||||||
|
properties:
|
||||||
|
Type:
|
||||||
|
type: string
|
||||||
|
Value:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
models.CloudEventVm:
|
||||||
|
properties:
|
||||||
|
Name:
|
||||||
|
type: string
|
||||||
|
Vm:
|
||||||
|
properties:
|
||||||
|
Type:
|
||||||
|
type: string
|
||||||
|
Value:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
models.ImportReceived:
|
||||||
|
properties:
|
||||||
|
Cluster:
|
||||||
|
type: string
|
||||||
|
CreationTime:
|
||||||
|
type: integer
|
||||||
|
Datacenter:
|
||||||
|
type: string
|
||||||
|
Folder:
|
||||||
|
type: string
|
||||||
|
InitialRam:
|
||||||
|
type: integer
|
||||||
|
InitialVcpus:
|
||||||
|
type: integer
|
||||||
|
Name:
|
||||||
|
type: string
|
||||||
|
PowerState:
|
||||||
|
type: integer
|
||||||
|
ProvisionedDisk:
|
||||||
|
type: number
|
||||||
|
ResourcePool:
|
||||||
|
type: string
|
||||||
|
Vcenter:
|
||||||
|
type: string
|
||||||
|
VmId:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
info:
|
||||||
|
contact: {}
|
||||||
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
description: Renders the main UI page.
|
||||||
|
produces:
|
||||||
|
- text/html
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: HTML page
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Render failed
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Home page
|
||||||
|
tags:
|
||||||
|
- ui
|
||||||
|
/api/cleanup/updates:
|
||||||
|
delete:
|
||||||
|
description: Removes update records that are no longer associated with a VM.
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Cleanup completed
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Cleanup updates
|
||||||
|
tags:
|
||||||
|
- maintenance
|
||||||
|
/api/cleanup/vcenter:
|
||||||
|
delete:
|
||||||
|
description: Removes all inventory entries associated with a vCenter URL.
|
||||||
|
parameters:
|
||||||
|
- description: vCenter URL
|
||||||
|
in: query
|
||||||
|
name: vc_url
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Cleanup completed
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Cleanup vCenter inventory
|
||||||
|
tags:
|
||||||
|
- maintenance
|
||||||
|
/api/encrypt:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Encrypts a plaintext value and returns the ciphertext.
|
||||||
|
parameters:
|
||||||
|
- description: Plaintext payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Ciphertext response
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Encrypt data
|
||||||
|
tags:
|
||||||
|
- crypto
|
||||||
|
/api/event/vm/create:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Parses a VM create CloudEvent and stores the event data.
|
||||||
|
parameters:
|
||||||
|
- description: CloudEvent payload
|
||||||
|
in: body
|
||||||
|
name: event
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.CloudEventReceived'
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Create event processed
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Record VM create event
|
||||||
|
tags:
|
||||||
|
- events
|
||||||
|
/api/event/vm/delete:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Parses a VM delete CloudEvent and marks the VM as deleted in inventory.
|
||||||
|
parameters:
|
||||||
|
- description: CloudEvent payload
|
||||||
|
in: body
|
||||||
|
name: event
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.CloudEventReceived'
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Delete event processed
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Record VM delete event
|
||||||
|
tags:
|
||||||
|
- events
|
||||||
|
/api/event/vm/modify:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Parses a VM modify CloudEvent and creates an update record when
|
||||||
|
relevant changes are detected.
|
||||||
|
parameters:
|
||||||
|
- description: CloudEvent payload
|
||||||
|
in: body
|
||||||
|
name: event
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.CloudEventReceived'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Modify event processed
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"202":
|
||||||
|
description: No relevant changes
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Record VM modify event
|
||||||
|
tags:
|
||||||
|
- events
|
||||||
|
/api/event/vm/move:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Parses a VM move CloudEvent and creates an update record.
|
||||||
|
parameters:
|
||||||
|
- description: CloudEvent payload
|
||||||
|
in: body
|
||||||
|
name: event
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.CloudEventReceived'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Move event processed
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Record VM move event
|
||||||
|
tags:
|
||||||
|
- events
|
||||||
|
/api/import/vm:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Imports existing VM inventory data in bulk.
|
||||||
|
parameters:
|
||||||
|
- description: Bulk import payload
|
||||||
|
in: body
|
||||||
|
name: import
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.ImportReceived'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Import processed
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Import VMs
|
||||||
|
tags:
|
||||||
|
- inventory
|
||||||
|
/api/inventory/vm/delete:
|
||||||
|
delete:
|
||||||
|
description: Removes a VM inventory entry by VM ID and datacenter name.
|
||||||
|
parameters:
|
||||||
|
- description: VM ID
|
||||||
|
in: query
|
||||||
|
name: vm_id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Datacenter name
|
||||||
|
in: query
|
||||||
|
name: datacenter_name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Cleanup completed
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Cleanup VM inventory entry
|
||||||
|
tags:
|
||||||
|
- inventory
|
||||||
|
/api/inventory/vm/update:
|
||||||
|
post:
|
||||||
|
description: Queries vCenter and updates inventory records with missing details.
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Update completed
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Refresh VM details
|
||||||
|
tags:
|
||||||
|
- inventory
|
||||||
|
/api/report/inventory:
|
||||||
|
get:
|
||||||
|
description: Generates an inventory XLSX report and returns it as a file download.
|
||||||
|
produces:
|
||||||
|
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Inventory XLSX report
|
||||||
|
schema:
|
||||||
|
type: file
|
||||||
|
"500":
|
||||||
|
description: Report generation failed
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Download inventory report
|
||||||
|
tags:
|
||||||
|
- reports
|
||||||
|
/api/report/snapshot:
|
||||||
|
get:
|
||||||
|
description: Downloads a snapshot table as an XLSX file.
|
||||||
|
parameters:
|
||||||
|
- description: Snapshot table name
|
||||||
|
in: query
|
||||||
|
name: table
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Snapshot XLSX report
|
||||||
|
schema:
|
||||||
|
type: file
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Download snapshot report
|
||||||
|
tags:
|
||||||
|
- snapshots
|
||||||
|
/api/report/updates:
|
||||||
|
get:
|
||||||
|
description: Generates an updates XLSX report and returns it as a file download.
|
||||||
|
produces:
|
||||||
|
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Updates XLSX report
|
||||||
|
schema:
|
||||||
|
type: file
|
||||||
|
"500":
|
||||||
|
description: Report generation failed
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Download updates report
|
||||||
|
tags:
|
||||||
|
- reports
|
||||||
|
/snapshots/daily:
|
||||||
|
get:
|
||||||
|
description: Lists daily summary snapshot tables.
|
||||||
|
produces:
|
||||||
|
- text/html
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: HTML page
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: List daily snapshots
|
||||||
|
tags:
|
||||||
|
- snapshots
|
||||||
|
/snapshots/hourly:
|
||||||
|
get:
|
||||||
|
description: Lists hourly inventory snapshot tables.
|
||||||
|
produces:
|
||||||
|
- text/html
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: HTML page
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: List hourly snapshots
|
||||||
|
tags:
|
||||||
|
- snapshots
|
||||||
|
/snapshots/monthly:
|
||||||
|
get:
|
||||||
|
description: Lists monthly summary snapshot tables.
|
||||||
|
produces:
|
||||||
|
- text/html
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: HTML page
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Server error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: List monthly snapshots
|
||||||
|
tags:
|
||||||
|
- snapshots
|
||||||
|
swagger: "2.0"
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
@@ -46,10 +47,31 @@ func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver st
|
|||||||
|
|
||||||
mux.HandleFunc("/api/report/inventory", h.InventoryReportDownload)
|
mux.HandleFunc("/api/report/inventory", h.InventoryReportDownload)
|
||||||
mux.HandleFunc("/api/report/updates", h.UpdateReportDownload)
|
mux.HandleFunc("/api/report/updates", h.UpdateReportDownload)
|
||||||
|
mux.HandleFunc("/api/report/snapshot", h.SnapshotReportDownload)
|
||||||
|
|
||||||
|
mux.HandleFunc("/snapshots/hourly", h.SnapshotHourlyList)
|
||||||
|
mux.HandleFunc("/snapshots/daily", h.SnapshotDailyList)
|
||||||
|
mux.HandleFunc("/snapshots/monthly", h.SnapshotMonthlyList)
|
||||||
|
|
||||||
// endpoint for encrypting vcenter credential
|
// endpoint for encrypting vcenter credential
|
||||||
mux.HandleFunc("/api/encrypt", h.EncryptData)
|
mux.HandleFunc("/api/encrypt", h.EncryptData)
|
||||||
|
|
||||||
|
// serve swagger related components from the embedded fs
|
||||||
|
swaggerSub, err := fs.Sub(swaggerUI, "swagger-ui-dist")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to load swagger ui assets", "error", err)
|
||||||
|
} else {
|
||||||
|
mux.Handle("/swagger/", http.StripPrefix("/swagger/", http.FileServer(http.FS(swaggerSub))))
|
||||||
|
}
|
||||||
|
mux.HandleFunc("/swagger", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/swagger/", http.StatusPermanentRedirect)
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/swagger.json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(swaggerSpec)
|
||||||
|
})
|
||||||
|
|
||||||
// Register pprof handlers
|
// Register pprof handlers
|
||||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
|
|||||||
11
server/router/swagger_embed.go
Normal file
11
server/router/swagger_embed.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed swagger-ui-dist/*
|
||||||
|
var swaggerUI embed.FS
|
||||||
|
|
||||||
|
//go:embed docs/swagger.json
|
||||||
|
var swaggerSpec []byte
|
||||||
Reference in New Issue
Block a user