This commit is contained in:
18
.drone.yml
18
.drone.yml
@@ -17,6 +17,7 @@ steps:
|
|||||||
mount:
|
mount:
|
||||||
- pkg.mod
|
- pkg.mod
|
||||||
- pkg.build
|
- pkg.build
|
||||||
|
- pkg.tools
|
||||||
volumes:
|
volumes:
|
||||||
- name: cache
|
- name: cache
|
||||||
path: /go
|
path: /go
|
||||||
@@ -36,13 +37,27 @@ steps:
|
|||||||
- go install github.com/a-h/templ/cmd/templ@latest
|
- go install github.com/a-h/templ/cmd/templ@latest
|
||||||
- go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
- go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||||
- go install github.com/swaggo/swag/cmd/swag@latest
|
- go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
- go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest
|
||||||
- sqlc generate
|
- sqlc generate
|
||||||
- templ generate -path ./components
|
- templ generate -path ./components
|
||||||
- swag init --exclude "pkg.mod,pkg.build,pkg.tools" -o server/router/docs
|
- swag init --exclude "pkg.mod,pkg.build,pkg.tools" -o server/router/docs
|
||||||
- chmod +x ./scripts/*.sh
|
- chmod +x ./scripts/*.sh
|
||||||
- ./scripts/update-swagger-ui.sh
|
- ./scripts/update-swagger-ui.sh
|
||||||
- ./scripts/drone.sh
|
- ./scripts/drone.sh
|
||||||
- cp ./build/cbs-linux-amd64 /shared/
|
- cp ./build/vctp-linux-amd64 /shared/
|
||||||
|
|
||||||
|
- name: rpm
|
||||||
|
image: ghcr.io/goreleaser/nfpm
|
||||||
|
environment:
|
||||||
|
TZ: UTC
|
||||||
|
volumes:
|
||||||
|
- name: shared
|
||||||
|
path: /shared
|
||||||
|
commands:
|
||||||
|
- cp /shared/vctp-linux-amd64 ./build/vctp-linux-amd64
|
||||||
|
#- find .
|
||||||
|
- nfpm package --config vctp.yml --packager rpm --target ./build/
|
||||||
|
- ls -lah ./build/
|
||||||
|
|
||||||
- name: dell-sftp-deploy
|
- name: dell-sftp-deploy
|
||||||
image: hypervtechnics/drone-sftp
|
image: hypervtechnics/drone-sftp
|
||||||
@@ -76,6 +91,7 @@ steps:
|
|||||||
mount:
|
mount:
|
||||||
- pkg.mod
|
- pkg.mod
|
||||||
- pkg.build
|
- pkg.build
|
||||||
|
- pkg.tools
|
||||||
volumes:
|
volumes:
|
||||||
- name: cache
|
- name: cache
|
||||||
path: /go
|
path: /go
|
||||||
|
|||||||
232
README.md
232
README.md
@@ -1,110 +1,14 @@
|
|||||||
# Go + HTMX Template
|
# Initial setup
|
||||||
|
|
||||||
This is built from the template https://github.com/Piszmog/go-htmx-template that comes with everything you need to build a Web Application using Go (templ) and HTMX.
|
## Pre-requisite tools
|
||||||
|
|
||||||
The template comes with a basic structure of using a SQL DB (`sqlc`), E2E testing (playwright), and styling (tailwindcss).
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
Clone https://github.com/Piszmog/go-htmx-template
|
|
||||||
|
|
||||||
Once cloned, run the `update_module.sh` script to change the module to your module name.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./update_module my-new-module
|
go install github.com/a-h/templ/cmd/templ@latest
|
||||||
|
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||||
|
go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## Technologies
|
## Database
|
||||||
|
|
||||||
A few different technologies are configured to help getting off the ground easier.
|
|
||||||
|
|
||||||
- [sqlc](https://sqlc.dev/) for database layer
|
|
||||||
- Stubbed to use SQLite
|
|
||||||
- This can be easily swapped with [sqlx](https://jmoiron.github.io/sqlx/)
|
|
||||||
- The script `upgrade_sqlc.sh` is available to upgrade GitHub Workflow files to latest sqlc version
|
|
||||||
- [Tailwind CSS](https://tailwindcss.com/) for styling
|
|
||||||
- Output is generated with the [CLI](https://tailwindcss.com/docs/installation)
|
|
||||||
- [templ](https://templ.guide/) for creating HTML
|
|
||||||
- The script `upgrade_templ.sh` is available to make upgrading easier
|
|
||||||
- [HTMX](https://htmx.org/) for HTML interaction
|
|
||||||
- The script `upgrade_htmx.sh` is available to make upgrading easier
|
|
||||||
- [goose](https://github.com/pressly/goose) for DB migrations
|
|
||||||
|
|
||||||
TODO: investigate https://github.com/DATA-DOG/go-sqlmock for testing
|
|
||||||
|
|
||||||
Technologies we're no longer using:
|
|
||||||
- [golang migrate](https://github.com/golang-migrate/migrate) for DB migrations
|
|
||||||
- [playwright-go](https://github.com/playwright-community/playwright-go) for E2E testing.
|
|
||||||
|
|
||||||
Everything else uses the standard library.
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
(Now out of date)
|
|
||||||
|
|
||||||
```text
|
|
||||||
.
|
|
||||||
├── Makefile
|
|
||||||
├── components
|
|
||||||
│ ├── core
|
|
||||||
│ │ └── html.templ
|
|
||||||
│ └── home
|
|
||||||
│ └── home.templ
|
|
||||||
├── db
|
|
||||||
│ ├── db.go
|
|
||||||
│ ├── local.go
|
|
||||||
│ ├── migrations
|
|
||||||
│ │ ├── 20240407203525_init.down.sql
|
|
||||||
│ │ └── 20240407203525_init.up.sql
|
|
||||||
│ └── queries
|
|
||||||
│ └── query.sql
|
|
||||||
├── db.sqlite3
|
|
||||||
├── dist
|
|
||||||
│ ├── assets
|
|
||||||
│ │ └── js
|
|
||||||
│ │ └── htmx@1.9.10.min.js
|
|
||||||
│ └── dist.go
|
|
||||||
├── e2e
|
|
||||||
│ ├── e2e_test.go
|
|
||||||
│ ├── home_test.go
|
|
||||||
│ └── testdata
|
|
||||||
│ └── seed.sql
|
|
||||||
├── go.mod
|
|
||||||
├── go.sum
|
|
||||||
├── log
|
|
||||||
│ └── log.go
|
|
||||||
├── main.go
|
|
||||||
├── server
|
|
||||||
│ ├── handler
|
|
||||||
│ │ ├── handler.go
|
|
||||||
│ │ └── home.go
|
|
||||||
│ ├── middleware
|
|
||||||
│ │ ├── cache.go
|
|
||||||
│ │ ├── logging.go
|
|
||||||
│ │ └── middleware.go
|
|
||||||
│ ├── router
|
|
||||||
│ │ └── router.go
|
|
||||||
│ └── server.go
|
|
||||||
├── sqlc.yml
|
|
||||||
├── styles
|
|
||||||
│ └── input.css
|
|
||||||
├── tailwind.config.js
|
|
||||||
└── version
|
|
||||||
└── version.go
|
|
||||||
```
|
|
||||||
|
|
||||||
### Components
|
|
||||||
|
|
||||||
This is where `templ` files live. Anything you want to render to the user goes here. Note, all
|
|
||||||
`*.go` files will be ignored by `git` (configured in `.gitignore`).
|
|
||||||
|
|
||||||
### DB
|
|
||||||
|
|
||||||
This is the directory that `sqlc` generates to. Update `queries.sql` to build
|
|
||||||
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
|
|
||||||
|
|
||||||
#### DB Migrations
|
|
||||||
This project now uses [goose](https://github.com/pressly/goose) for DB migrations.
|
This project now uses [goose](https://github.com/pressly/goose) for DB migrations.
|
||||||
|
|
||||||
Install via `brew install goose` on a mac, or install via golang with command `go install github.com/pressly/goose/v3/cmd/goose@latest`
|
Install via `brew install goose` on a mac, or install via golang with command `go install github.com/pressly/goose/v3/cmd/goose@latest`
|
||||||
@@ -114,6 +18,16 @@ Create a new up/down migration file with this command
|
|||||||
goose -dir db/migrations sqlite3 ./db.sqlite3 create init sql
|
goose -dir db/migrations sqlite3 ./db.sqlite3 create init sql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sqlc generate
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTML templates
|
||||||
|
Run `templ generate -path ./components` to generate code based on template files
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
Run `swag init --exclude "pkg.mod,pkg.build,pkg.tools" -o server/router/docs`
|
||||||
|
|
||||||
#### Database Configuration
|
#### Database Configuration
|
||||||
By default the app uses SQLite and creates/opens `db.sqlite3`. You can opt into PostgreSQL
|
By default the app uses SQLite and creates/opens `db.sqlite3`. You can opt into PostgreSQL
|
||||||
by setting environment variables:
|
by setting environment variables:
|
||||||
@@ -138,117 +52,3 @@ Hourly and daily snapshot table retention can be configured with environment var
|
|||||||
|
|
||||||
- `HOURLY_SNAPSHOT_MAX_AGE_DAYS` (default: 60)
|
- `HOURLY_SNAPSHOT_MAX_AGE_DAYS` (default: 60)
|
||||||
- `DAILY_SNAPSHOT_MAX_AGE_MONTHS` (default: 12)
|
- `DAILY_SNAPSHOT_MAX_AGE_MONTHS` (default: 12)
|
||||||
|
|
||||||
### Dist
|
|
||||||
|
|
||||||
This is where your assets live. Any Javascript, images, or styling needs to go in the
|
|
||||||
`dist/assets` directory. The directory will be embedded into the application.
|
|
||||||
|
|
||||||
Note, the `dist/assets/css` will be ignored by `git` (configured in `.gitignore`) since the
|
|
||||||
files that are written to this directory are done by the Tailwind CSS CLI. Custom styles should
|
|
||||||
go in the `styles/input.css` file.
|
|
||||||
|
|
||||||
### E2E
|
|
||||||
|
|
||||||
To test the UI, the `e2e` directory contains the Go tests for performing End to end testing. To
|
|
||||||
run the tests, run the command
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go test -v ./... -tags=e2e
|
|
||||||
```
|
|
||||||
|
|
||||||
The end to end tests, will start up the app, on a random port, seeding the database using the
|
|
||||||
`seed.sql` file. Once the tests are complete, the app will be stopped.
|
|
||||||
|
|
||||||
The E2E tests use Playwright (Go) for better integration into the Go tooling.
|
|
||||||
|
|
||||||
### Log
|
|
||||||
|
|
||||||
This contains helper function to create a `slog.Logger`. Log level and output type can be set
|
|
||||||
with then environment variables `LOG_LEVEL` and `LOG_OUTPUT`. The logger will write to
|
|
||||||
`stdout`.
|
|
||||||
|
|
||||||
### Server
|
|
||||||
|
|
||||||
This contains everything related to the HTTP server. It comes with a graceful shutdown handler
|
|
||||||
that handles `SIGINT`.
|
|
||||||
|
|
||||||
#### Router
|
|
||||||
|
|
||||||
This package sets up the routing for the application, such as the `/assets/` path and `/` path.
|
|
||||||
It uses the standard libraries mux for routing. You can easily swap out for other HTTP
|
|
||||||
routers such as [gorilla/mux](https://github.com/gorilla/mux).
|
|
||||||
|
|
||||||
#### Middleware
|
|
||||||
|
|
||||||
This package contains any middleware to configured with routes.
|
|
||||||
|
|
||||||
#### Handler
|
|
||||||
|
|
||||||
This package contains the handler to handle the actual routes.
|
|
||||||
|
|
||||||
#### Styles
|
|
||||||
|
|
||||||
This contains the `input.css` that the Tailwind CSS CLI uses to generate your output CSS.
|
|
||||||
Update `input.css` with any custom CSS you need and it will be included in the output CSS.
|
|
||||||
|
|
||||||
#### Version
|
|
||||||
|
|
||||||
This package allows you to set a version at build time. If not set, the version defaults to
|
|
||||||
`dev`. To set the version run the following command,
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go build -o ./app -ldflags="-X version.Value=1.0.0"
|
|
||||||
```
|
|
||||||
|
|
||||||
See the `Makefile` for building the application.
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
There are a couple builtin ways to run the application - using `air` or the `Makefile` helper
|
|
||||||
commands.
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Install [templ](https://templ.guide/quick-start/installation) - `go install github.com/a-h/templ/cmd/templ@latest`
|
|
||||||
- Install [sqlc](https://docs.sqlc.dev/en/stable/overview/install.html)
|
|
||||||
- Install [tailwindcss CLI](https://tailwindcss.com/docs/installation)
|
|
||||||
|
|
||||||
#### tailwindcss
|
|
||||||
```shell
|
|
||||||
# Example for macOS arm64
|
|
||||||
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64
|
|
||||||
chmod +x tailwindcss-macos-arm64
|
|
||||||
sudo mv tailwindcss-macos-arm64 /usr/local/bin/tailwindcss
|
|
||||||
```
|
|
||||||
|
|
||||||
### Makefile
|
|
||||||
|
|
||||||
You can also run with the provided `Makefile`. There are commands to generate `templ` files and
|
|
||||||
tailwind output css.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Generate and watch templ
|
|
||||||
make generate-templ-watch
|
|
||||||
|
|
||||||
# Genrate and watch tailwindcss
|
|
||||||
make generate-tailwind-watch
|
|
||||||
|
|
||||||
# Run application
|
|
||||||
make run
|
|
||||||
```
|
|
||||||
|
|
||||||
## Github Workflow
|
|
||||||
|
|
||||||
The repository comes with two Github workflows as well. One called `ci.yml` that lints and
|
|
||||||
tests your code. The other called `release.yml` that creates a tag, GitHub Release, and
|
|
||||||
attaches the Linux binary to the Release.
|
|
||||||
|
|
||||||
Note, the version of `github.com/a-h/templ/cmd/templ` matches the version in `go.mod`. If these
|
|
||||||
do not match, the build will fail. When upgrading your `templ` version, make sure to update
|
|
||||||
`ci.yml` and `release.yml`.
|
|
||||||
|
|
||||||
### GoReleaser
|
|
||||||
|
|
||||||
If you need to compile for more than Linux, see [GoReleaser](https://goreleaser.com/) for a
|
|
||||||
better release process.
|
|
||||||
|
|||||||
@@ -10,5 +10,78 @@ templ Header() {
|
|||||||
<title>vCTP API</title>
|
<title>vCTP API</title>
|
||||||
<script src="/assets/js/htmx@v2.0.2.min.js"></script>
|
<script src="/assets/js/htmx@v2.0.2.min.js"></script>
|
||||||
<link href={ "/assets/css/output@" + version.Value + ".css" } rel="stylesheet"/>
|
<link href={ "/assets/css/output@" + version.Value + ".css" } rel="stylesheet"/>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--web2-blue: #3b82f6;
|
||||||
|
--web2-cyan: #22d3ee;
|
||||||
|
--web2-slate: #0f172a;
|
||||||
|
--web2-card: #ffffff;
|
||||||
|
--web2-shadow: 0 20px 40px rgba(15, 23, 42, 0.15);
|
||||||
|
--web2-soft-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: "Trebuchet MS", "Lucida Grande", "Verdana", sans-serif;
|
||||||
|
color: var(--web2-slate);
|
||||||
|
}
|
||||||
|
.web2-bg {
|
||||||
|
background: radial-gradient(circle at top left, #e0f2fe 0%, #f8fafc 45%, #e2e8f0 100%);
|
||||||
|
}
|
||||||
|
.web2-shell {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1.5rem 4rem;
|
||||||
|
}
|
||||||
|
.web2-header {
|
||||||
|
background: linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 22px;
|
||||||
|
box-shadow: var(--web2-shadow);
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
}
|
||||||
|
.web2-card {
|
||||||
|
background: var(--web2-card);
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: var(--web2-soft-shadow);
|
||||||
|
padding: 1.5rem 1.75rem;
|
||||||
|
}
|
||||||
|
.web2-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||||
|
padding: 0.35rem 0.8rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
.web2-link {
|
||||||
|
color: var(--web2-blue);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.web2-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.web2-button {
|
||||||
|
background: linear-gradient(180deg, #60a5fa 0%, #2563eb 100%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow: 0 6px 16px rgba(37, 99, 235, 0.35);
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.web2-button:hover {
|
||||||
|
filter: brightness(1.05);
|
||||||
|
}
|
||||||
|
.web2-list li {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0.85rem 1.1rem;
|
||||||
|
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
}
|
}
|
||||||
@@ -38,13 +38,13 @@ func Header() templ.Component {
|
|||||||
var templ_7745c5c3_Var2 templ.SafeURL
|
var templ_7745c5c3_Var2 templ.SafeURL
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs("/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: `components/core/header.templ`, Line: 12, Col: 61}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `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 = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" rel=\"stylesheet\"></head>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" rel=\"stylesheet\"><style>\n\t\t\t:root {\n\t\t\t\t--web2-blue: #3b82f6;\n\t\t\t\t--web2-cyan: #22d3ee;\n\t\t\t\t--web2-slate: #0f172a;\n\t\t\t\t--web2-card: #ffffff;\n\t\t\t\t--web2-shadow: 0 20px 40px rgba(15, 23, 42, 0.15);\n\t\t\t\t--web2-soft-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);\n\t\t\t}\n\t\t\tbody {\n\t\t\t\tfont-family: \"Trebuchet MS\", \"Lucida Grande\", \"Verdana\", sans-serif;\n\t\t\t\tcolor: var(--web2-slate);\n\t\t\t}\n\t\t\t.web2-bg {\n\t\t\t\tbackground: radial-gradient(circle at top left, #e0f2fe 0%, #f8fafc 45%, #e2e8f0 100%);\n\t\t\t}\n\t\t\t.web2-shell {\n\t\t\t\tmax-width: 1100px;\n\t\t\t\tmargin: 0 auto;\n\t\t\t\tpadding: 2rem 1.5rem 4rem;\n\t\t\t}\n\t\t\t.web2-header {\n\t\t\t\tbackground: linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%);\n\t\t\t\tcolor: #fff;\n\t\t\t\tborder-radius: 22px;\n\t\t\t\tbox-shadow: var(--web2-shadow);\n\t\t\t\tpadding: 1.5rem 2rem;\n\t\t\t}\n\t\t\t.web2-card {\n\t\t\t\tbackground: var(--web2-card);\n\t\t\t\tborder-radius: 18px;\n\t\t\t\tbox-shadow: var(--web2-soft-shadow);\n\t\t\t\tpadding: 1.5rem 1.75rem;\n\t\t\t}\n\t\t\t.web2-pill {\n\t\t\t\tdisplay: inline-flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: 0.4rem;\n\t\t\t\tbackground: rgba(255, 255, 255, 0.2);\n\t\t\t\tborder: 1px solid rgba(255, 255, 255, 0.35);\n\t\t\t\tpadding: 0.35rem 0.8rem;\n\t\t\t\tborder-radius: 999px;\n\t\t\t\tfont-size: 0.85rem;\n\t\t\t\tletter-spacing: 0.02em;\n\t\t\t\tbackdrop-filter: blur(6px);\n\t\t\t}\n\t\t\t.web2-link {\n\t\t\t\tcolor: var(--web2-blue);\n\t\t\t\ttext-decoration: none;\n\t\t\t\tfont-weight: 600;\n\t\t\t}\n\t\t\t.web2-link:hover {\n\t\t\t\ttext-decoration: underline;\n\t\t\t}\n\t\t\t.web2-button {\n\t\t\t\tbackground: linear-gradient(180deg, #60a5fa 0%, #2563eb 100%);\n\t\t\t\tcolor: #fff;\n\t\t\t\tpadding: 0.5rem 1rem;\n\t\t\t\tborder-radius: 999px;\n\t\t\t\tbox-shadow: 0 6px 16px rgba(37, 99, 235, 0.35);\n\t\t\t\tfont-weight: 600;\n\t\t\t\ttext-decoration: none;\n\t\t\t}\n\t\t\t.web2-button:hover {\n\t\t\t\tfilter: brightness(1.05);\n\t\t\t}\n\t\t\t.web2-list li {\n\t\t\t\tbackground: rgba(255, 255, 255, 0.9);\n\t\t\t\tborder-radius: 14px;\n\t\t\t\tpadding: 0.85rem 1.1rem;\n\t\t\t\tbox-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);\n\t\t\t}\n\t\t</style></head>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,39 @@ templ Index(info BuildInfo) {
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@core.Header()
|
@core.Header()
|
||||||
<body class="flex flex-col min-h-screen">
|
<body class="flex flex-col min-h-screen web2-bg">
|
||||||
<main class="flex-grow">
|
<main class="flex-grow web2-shell space-y-8">
|
||||||
<div>
|
<section class="web2-header">
|
||||||
<h1 class="text-5xl font-bold">Build Information</h1>
|
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||||
<p class="mt-4"><strong>Build Time:</strong> {info.BuildTime}</p>
|
<div>
|
||||||
<p class="mt-4"><strong>SHA1 Version:</strong> {info.SHA1Ver}</p>
|
<div class="web2-pill">vCTP Console</div>
|
||||||
<p class="mt-4"><strong>Go Runtime Version:</strong> {info.GoVersion}</p>
|
<h1 class="mt-3 text-4xl font-bold">Build Intelligence Dashboard</h1>
|
||||||
</div>
|
<p class="mt-2 text-sm opacity-90">A glossy, snapshot-ready view of what is running.</p>
|
||||||
</main>
|
</div>
|
||||||
</body>
|
<div class="flex flex-wrap gap-3">
|
||||||
@core.Footer()
|
<a class="web2-button" href="/snapshots/hourly">Hourly Snapshots</a>
|
||||||
|
<a class="web2-button" href="/snapshots/daily">Daily Snapshots</a>
|
||||||
|
<a class="web2-button" href="/snapshots/monthly">Monthly Snapshots</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="grid gap-6 md:grid-cols-3">
|
||||||
|
<div class="web2-card">
|
||||||
|
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Build Time</p>
|
||||||
|
<p class="mt-3 text-xl font-semibold">{info.BuildTime}</p>
|
||||||
|
</div>
|
||||||
|
<div class="web2-card">
|
||||||
|
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">SHA1 Version</p>
|
||||||
|
<p class="mt-3 text-xl font-semibold">{info.SHA1Ver}</p>
|
||||||
|
</div>
|
||||||
|
<div class="web2-card">
|
||||||
|
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Go Runtime</p>
|
||||||
|
<p class="mt-3 text-xl font-semibold">{info.GoVersion}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
@core.Footer()
|
||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
@@ -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 = 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> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell space-y-8\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">vCTP Console</div><h1 class=\"mt-3 text-4xl font-bold\">Build Intelligence Dashboard</h1><p class=\"mt-2 text-sm opacity-90\">A glossy, snapshot-ready view of what is running.</p></div><div class=\"flex flex-wrap gap-3\"><a class=\"web2-button\" href=\"/snapshots/hourly\">Hourly Snapshots</a> <a class=\"web2-button\" href=\"/snapshots/daily\">Daily Snapshots</a> <a class=\"web2-button\" href=\"/snapshots/monthly\">Monthly Snapshots</a></div></div></section><section class=\"grid gap-6 md:grid-cols-3\"><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">Build Time</p><p class=\"mt-3 text-xl font-semibold\">")
|
||||||
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: `components/views/index.templ`, Line: 21, Col: 80}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 37, Col: 59}
|
||||||
}
|
}
|
||||||
_, 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p><p class=\"mt-4\"><strong>SHA1 Version:</strong> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p></div><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">SHA1 Version</p><p class=\"mt-3 text-xl font-semibold\">")
|
||||||
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: `components/views/index.templ`, Line: 22, Col: 80}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 41, Col: 57}
|
||||||
}
|
}
|
||||||
_, 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><p class=\"mt-4\"><strong>Go Runtime Version:</strong> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p></div><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">Go Runtime</p><p class=\"mt-3 text-xl font-semibold\">")
|
||||||
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: `components/views/index.templ`, Line: 23, Col: 88}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 45, Col: 59}
|
||||||
}
|
}
|
||||||
_, 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p></div></main></body>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p></div></section></main></body>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,21 +25,33 @@ templ SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) {
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@core.Header()
|
@core.Header()
|
||||||
<body class="flex flex-col min-h-screen">
|
<body class="flex flex-col min-h-screen web2-bg">
|
||||||
<main class="flex-grow">
|
<main class="flex-grow web2-shell space-y-8">
|
||||||
<div>
|
<section class="web2-header">
|
||||||
<h1 class="text-4xl font-bold">{title}</h1>
|
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||||
<p class="mt-2 text-gray-600">{subtitle}</p>
|
<div>
|
||||||
<ul class="mt-6 space-y-2">
|
<div class="web2-pill">Snapshot Library</div>
|
||||||
|
<h1 class="mt-3 text-4xl font-bold">{title}</h1>
|
||||||
|
<p class="mt-2 text-sm opacity-90">{subtitle}</p>
|
||||||
|
</div>
|
||||||
|
<a class="web2-button" href="/">Back to Dashboard</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="web2-card">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="text-lg font-semibold">Available Exports</h2>
|
||||||
|
<span class="text-xs uppercase tracking-[0.2em] text-slate-400">{len(entries)} files</span>
|
||||||
|
</div>
|
||||||
|
<ul class="mt-6 space-y-3 web2-list">
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
<li>
|
<li class="flex items-center justify-between gap-4">
|
||||||
<a class="text-blue-600 hover:underline" href={entry.Link}>
|
<span class="text-sm font-semibold text-slate-700">{entry.Label}</span>
|
||||||
{entry.Label}
|
<a class="web2-link" href={entry.Link}>Download XLSX</a>
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
@core.Footer()
|
@core.Footer()
|
||||||
|
|||||||
@@ -133,69 +133,82 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
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\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell space-y-8\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">Snapshot Library</div><h1 class=\"mt-3 text-4xl font-bold\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var5 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 31, Col: 42}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 34, Col: 49}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1><p class=\"mt-2 text-gray-600\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1><p class=\"mt-2 text-sm opacity-90\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(subtitle)
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(subtitle)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 32, Col: 44}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 35, Col: 51}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><ul class=\"mt-6 space-y-2\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p></div><a class=\"web2-button\" href=\"/\">Back to Dashboard</a></div></section><section class=\"web2-card\"><div class=\"flex items-center justify-between\"><h2 class=\"text-lg font-semibold\">Available Exports</h2><span class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(len(entries))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 44, Col: 83}
|
||||||
|
}
|
||||||
|
_, 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, 5, " files</span></div><ul class=\"mt-6 space-y-3 web2-list\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<li><a class=\"text-blue-600 hover:underline\" href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<li class=\"flex items-center justify-between gap-4\"><span class=\"text-sm font-semibold text-slate-700\">")
|
||||||
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var8 string
|
var templ_7745c5c3_Var8 string
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Label)
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Label)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 37, Col: 21}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 49, Col: 71}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</a></li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span> <a class=\"web2-link\" href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinURLErrs(entry.Link)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 50, Col: 45}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Download XLSX</a></li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</ul></div></main></body>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</ul></section></main></body>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -203,7 +216,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import (
|
|||||||
type Querier interface {
|
type Querier interface {
|
||||||
CleanupUpdates(ctx context.Context, arg queries.CleanupUpdatesParams) error
|
CleanupUpdates(ctx context.Context, arg queries.CleanupUpdatesParams) error
|
||||||
CleanupUpdatesNullVm(ctx context.Context) error
|
CleanupUpdatesNullVm(ctx context.Context) error
|
||||||
CreateEvent(ctx context.Context, arg queries.CreateEventParams) (queries.Events, error)
|
CreateEvent(ctx context.Context, arg queries.CreateEventParams) (queries.Event, error)
|
||||||
CreateInventory(ctx context.Context, arg queries.CreateInventoryParams) (queries.Inventory, error)
|
CreateInventory(ctx context.Context, arg queries.CreateInventoryParams) (queries.Inventory, error)
|
||||||
CreateInventoryHistory(ctx context.Context, arg queries.CreateInventoryHistoryParams) (queries.InventoryHistory, error)
|
CreateInventoryHistory(ctx context.Context, arg queries.CreateInventoryHistoryParams) (queries.InventoryHistory, error)
|
||||||
CreateUpdate(ctx context.Context, arg queries.CreateUpdateParams) (queries.Updates, error)
|
CreateUpdate(ctx context.Context, arg queries.CreateUpdateParams) (queries.Update, error)
|
||||||
GetInventoryByName(ctx context.Context, name string) ([]queries.Inventory, error)
|
GetInventoryByName(ctx context.Context, name string) ([]queries.Inventory, error)
|
||||||
GetInventoryByVcenter(ctx context.Context, vcenter string) ([]queries.Inventory, error)
|
GetInventoryByVcenter(ctx context.Context, vcenter string) ([]queries.Inventory, error)
|
||||||
GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (queries.Inventory, error)
|
GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (queries.Inventory, error)
|
||||||
@@ -21,15 +21,15 @@ type Querier interface {
|
|||||||
GetInventoryVmId(ctx context.Context, arg queries.GetInventoryVmIdParams) (queries.Inventory, error)
|
GetInventoryVmId(ctx context.Context, arg queries.GetInventoryVmIdParams) (queries.Inventory, error)
|
||||||
GetInventoryVmUuid(ctx context.Context, arg queries.GetInventoryVmUuidParams) (queries.Inventory, error)
|
GetInventoryVmUuid(ctx context.Context, arg queries.GetInventoryVmUuidParams) (queries.Inventory, error)
|
||||||
GetReportInventory(ctx context.Context) ([]queries.Inventory, error)
|
GetReportInventory(ctx context.Context) ([]queries.Inventory, error)
|
||||||
GetReportUpdates(ctx context.Context) ([]queries.Updates, error)
|
GetReportUpdates(ctx context.Context) ([]queries.Update, error)
|
||||||
GetVmUpdates(ctx context.Context, arg queries.GetVmUpdatesParams) ([]queries.Updates, error)
|
GetVmUpdates(ctx context.Context, arg queries.GetVmUpdatesParams) ([]queries.Update, error)
|
||||||
InventoryCleanup(ctx context.Context, arg queries.InventoryCleanupParams) error
|
InventoryCleanup(ctx context.Context, arg queries.InventoryCleanupParams) error
|
||||||
InventoryCleanupTemplates(ctx context.Context) error
|
InventoryCleanupTemplates(ctx context.Context) error
|
||||||
InventoryCleanupVcenter(ctx context.Context, vc string) error
|
InventoryCleanupVcenter(ctx context.Context, vc string) error
|
||||||
InventoryMarkDeleted(ctx context.Context, arg queries.InventoryMarkDeletedParams) error
|
InventoryMarkDeleted(ctx context.Context, arg queries.InventoryMarkDeletedParams) error
|
||||||
InventoryUpdate(ctx context.Context, arg queries.InventoryUpdateParams) error
|
InventoryUpdate(ctx context.Context, arg queries.InventoryUpdateParams) error
|
||||||
ListEvents(ctx context.Context) ([]queries.Events, error)
|
ListEvents(ctx context.Context) ([]queries.Event, error)
|
||||||
ListInventory(ctx context.Context) ([]queries.Inventory, error)
|
ListInventory(ctx context.Context) ([]queries.Inventory, error)
|
||||||
ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]queries.Events, error)
|
ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]queries.Event, error)
|
||||||
UpdateEventsProcessed(ctx context.Context, eid int64) error
|
UpdateEventsProcessed(ctx context.Context, eid int64) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
package_name=vctp
|
package_name=vctp
|
||||||
|
package=./
|
||||||
commit=$(git rev-parse HEAD)
|
commit=$(git rev-parse HEAD)
|
||||||
buildtime=$(date +%Y-%m-%dT%T%z)
|
buildtime=$(date +%Y-%m-%dT%T%z)
|
||||||
#Extract the version from yml
|
#Extract the version from yml
|
||||||
@@ -16,6 +17,7 @@ echo Building::
|
|||||||
echo - Version $package_version
|
echo - Version $package_version
|
||||||
echo - Commit $commit
|
echo - Commit $commit
|
||||||
echo - Build Time $buildtime
|
echo - Build Time $buildtime
|
||||||
|
mkdir -p build
|
||||||
for platform in "${platforms[@]}"
|
for platform in "${platforms[@]}"
|
||||||
do
|
do
|
||||||
platform_split=(${platform//\// })
|
platform_split=(${platform//\// })
|
||||||
@@ -38,6 +40,6 @@ do
|
|||||||
#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/
|
nfpm package --config $package_name.yml --packager rpm --target build/
|
||||||
|
|
||||||
ls -lah build
|
ls -lah build
|
||||||
|
|||||||
@@ -97,5 +97,4 @@ func (h *Handler) VmImport(w http.ResponseWriter, r *http.Request) {
|
|||||||
"status": "OK",
|
"status": "OK",
|
||||||
"message": fmt.Sprintf("Successfully processed import request for VM '%s'", inData.Name),
|
"message": fmt.Sprintf("Successfully processed import request for VM '%s'", inData.Name),
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"message": fmt.Sprintf("Received update event successfully but no config changes were found"),
|
"message": "Received update event successfully but no config changes were found",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -178,7 +178,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
h.Logger.Debug("Matched regex", "disk_owner", matches[1])
|
h.Logger.Debug("Matched regex", "disk_owner", matches[1])
|
||||||
|
|
||||||
if strings.ToLower(matches[1]) == strings.ToLower(event.CloudEvent.Data.VM.Name) {
|
if strings.EqualFold(matches[1], event.CloudEvent.Data.VM.Name) {
|
||||||
h.Logger.Debug("This disk belongs to this VM")
|
h.Logger.Debug("This disk belongs to this VM")
|
||||||
changeFound = true
|
changeFound = true
|
||||||
diskChangeFound = true
|
diskChangeFound = true
|
||||||
@@ -278,7 +278,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"message": fmt.Sprintf("Successfully processed vm modify event"),
|
"message": "Successfully processed vm modify event",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"message": fmt.Sprintf("Successfully processed vm modify event"),
|
"message": "Successfully processed vm modify event",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"status": "ERROR",
|
"status": "ERROR",
|
||||||
"message": fmt.Sprintf("CloudEvent missing resource pool data"),
|
"message": "CloudEvent missing resource pool data",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
//fmt.Fprintf(w, "Processed update event: %v\n", result)
|
//fmt.Fprintf(w, "Processed update event: %v\n", result)
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"message": fmt.Sprintf("Successfully processed move event"),
|
"message": "Successfully processed move event",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
6
sqlc.yml
6
sqlc.yml
@@ -1,11 +1,13 @@
|
|||||||
version: 2
|
version: 2
|
||||||
sql:
|
sql:
|
||||||
- engine: sqlite
|
- engine: sqlite
|
||||||
queries:
|
|
||||||
- db/queries/query.sql
|
|
||||||
schema:
|
schema:
|
||||||
- db/schema.sql
|
- db/schema.sql
|
||||||
|
queries:
|
||||||
|
- db/queries/query.sql
|
||||||
gen:
|
gen:
|
||||||
go:
|
go:
|
||||||
package: queries
|
package: queries
|
||||||
out: db/queries
|
out: db/queries
|
||||||
|
emit_json_tags: true
|
||||||
|
emit_db_tags: true
|
||||||
|
|||||||
10
src/postinstall.sh
Normal file
10
src/postinstall.sh
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl daemon-reload || :
|
||||||
|
if [ "$1" -eq 1 ]; then
|
||||||
|
systemctl enable --now vctp.service || :
|
||||||
|
else
|
||||||
|
systemctl try-restart vctp.service || :
|
||||||
|
fi
|
||||||
|
fi
|
||||||
8
src/postremove.sh
Normal file
8
src/postremove.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl daemon-reload || :
|
||||||
|
if [ "$1" -ge 1 ]; then
|
||||||
|
systemctl try-restart vctp.service || :
|
||||||
|
fi
|
||||||
|
fi
|
||||||
45
src/preinstall.sh
Normal file
45
src/preinstall.sh
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
USER="vctp"
|
||||||
|
GROUP="dtms"
|
||||||
|
|
||||||
|
# Path to the custom sudoers file
|
||||||
|
SUDOERS_FILE="/etc/sudoers.d/${USER}"
|
||||||
|
|
||||||
|
# create a group & user if not exists
|
||||||
|
getent group "$GROUP" >/dev/null || groupadd -r "$GROUP"; /bin/true
|
||||||
|
getent passwd "$USER" >/dev/null || useradd -r -g "$GROUP" -m -s /bin/bash -c "vctp service" "$USER"
|
||||||
|
getent passwd tftp >/dev/null || useradd -r -g tftp -s /sbin/nologin tftp
|
||||||
|
|
||||||
|
# create vctp config directory if it doesn't exist
|
||||||
|
[ -d /etc/dtms ] || mkdir -p /etc/dtms
|
||||||
|
|
||||||
|
# set group ownership on vctp config directory if not already done
|
||||||
|
[ "$(stat -c "%G" /etc/dtms)" = "$GROUP" ] || chgrp -R "$GROUP" /etc/dtms
|
||||||
|
|
||||||
|
# set permissions on vctp config directory if not already done
|
||||||
|
[ "$(stat -c "%a" /etc/dtms)" = "774" ] || chmod -R 774 /etc/dtms
|
||||||
|
|
||||||
|
# create vctp data directory if it doesn't exist
|
||||||
|
[ -d /var/lib/vctp ] || mkdir -p /var/lib/vctp
|
||||||
|
|
||||||
|
# set user ownership on vctp data directory if not already done
|
||||||
|
[ "$(stat -c "%U" /var/lib/vctp)" = "$USER" ] || chown -R "$USER" /var/lib/vctp
|
||||||
|
|
||||||
|
# set group ownership on vctp data directory if not already done
|
||||||
|
[ "$(stat -c "%G" /var/lib/vctp)" = "$GROUP" ] || chgrp -R "$GROUP" /var/lib/vctp
|
||||||
|
|
||||||
|
# Check if firewalld is installed and active
|
||||||
|
if command -v systemctl >/dev/null 2>&1 && systemctl is-enabled firewalld >/dev/null 2>&1 && systemctl is-active firewalld >/dev/null 2>&1; then
|
||||||
|
echo "Firewalld is enabled and running. Adding necessary ports..."
|
||||||
|
|
||||||
|
# Open HTTPS port (443/tcp)
|
||||||
|
firewall-cmd --permanent --add-service=https >/dev/null 2>&1
|
||||||
|
|
||||||
|
# Open custom application port (9443/tcp)
|
||||||
|
firewall-cmd --permanent --add-port=9443/tcp >/dev/null 2>&1
|
||||||
|
|
||||||
|
# Reload firewalld to apply changes
|
||||||
|
firewall-cmd --reload >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
echo "Firewalld is not running or not enabled. Skipping firewall configuration."
|
||||||
|
fi
|
||||||
8
src/preremove.sh
Normal file
8
src/preremove.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
if [ "$1" -eq 0 ]; then
|
||||||
|
systemctl stop vctp.service || :
|
||||||
|
systemctl disable vctp.service || :
|
||||||
|
fi
|
||||||
|
fi
|
||||||
1
src/vctp.default
Normal file
1
src/vctp.default
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CPE_OPTS='-config /etc/dtms/vctp.yml -log-level info -log-output text'
|
||||||
21
src/vctp.service
Normal file
21
src/vctp.service
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=vCTP monitors VMware VM inventory and event data to build chargeback reports
|
||||||
|
Documentation=https://gitlab.dell.com/
|
||||||
|
ConditionPathExists=/usr/bin/vctp-linux-amd64
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
EnvironmentFile=/etc/default/vctp
|
||||||
|
User=vctp
|
||||||
|
ExecStart=/usr/bin/vctp-linux-amd64 $CPE_OPTS
|
||||||
|
ExecStartPost=/usr/bin/sleep 3
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10s
|
||||||
|
#LimitNOFILE=65536
|
||||||
|
SyslogIdentifier=vctp
|
||||||
|
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_FOWNER CAP_DAC_OVERRIDE CAP_SETUID CAP_SETGID CAP_SYS_RESOURCE CAP_AUDIT_WRITE
|
||||||
|
#AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
25
src/vctp.yml
Normal file
25
src/vctp.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
settings:
|
||||||
|
data_location: "/var/lib/cbs"
|
||||||
|
kickstart_location: "/var/lib/cbs/ks"
|
||||||
|
database_filename: "/var/lib/cbs/cbs.db"
|
||||||
|
bind_ip:
|
||||||
|
bind_port: 443
|
||||||
|
bind_disable_tls: false
|
||||||
|
tls_cert_filename: "/etc/dtms/cbs.crt"
|
||||||
|
tls_key_filename: "/etc/dtms/cbs.key"
|
||||||
|
tftp_root_directory: "/var/lib/tftpboot"
|
||||||
|
tftp_images_subdirectory: "images"
|
||||||
|
replacements:
|
||||||
|
omapi:
|
||||||
|
key_name: "OMAPI"
|
||||||
|
key_secret:
|
||||||
|
special_files:
|
||||||
|
ldap_groups:
|
||||||
|
ldap_bind_address: ""
|
||||||
|
ldap_base_dn: ""
|
||||||
|
ldap_trust_cert_file: ""
|
||||||
|
ldap_disable_validation: false
|
||||||
|
ldap_insecure: false
|
||||||
|
auth_token_lifespan_hours: 2
|
||||||
|
auth_api_key: ""
|
||||||
|
|
||||||
40
vctp.yml
Normal file
40
vctp.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: "vctp"
|
||||||
|
arch: "amd64"
|
||||||
|
platform: "linux"
|
||||||
|
version: "v26.1.1"
|
||||||
|
version_schema: semver
|
||||||
|
description: vCTP monitors VMware VM inventory and event data to build chargeback reports
|
||||||
|
maintainer: "@coadn"
|
||||||
|
vendor: "Dell Technologies Managed Services"
|
||||||
|
homepage: "https://gitlab.dell.com/"
|
||||||
|
license: "Apache-2.0"
|
||||||
|
|
||||||
|
contents:
|
||||||
|
- src: build/vctp-linux-amd64
|
||||||
|
dst: /usr/bin/vctp-linux-amd64
|
||||||
|
|
||||||
|
- src: src/vctp.yml
|
||||||
|
dst: /etc/dtms/vctp.yml
|
||||||
|
type: config|noreplace
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: vctp
|
||||||
|
group: dtms
|
||||||
|
|
||||||
|
- src: src/vctp.service
|
||||||
|
dst: /usr/lib/systemd/system/vctp.service
|
||||||
|
|
||||||
|
- src: src/vctp.default
|
||||||
|
dst: /etc/default/vctp
|
||||||
|
type: config|noreplace
|
||||||
|
|
||||||
|
scripts:
|
||||||
|
preinstall: src/preinstall.sh
|
||||||
|
postinstall: src/postinstall.sh
|
||||||
|
preremove: src/preremove.sh
|
||||||
|
postremove: src/postremove.sh
|
||||||
|
|
||||||
|
depends:
|
||||||
|
- systemd
|
||||||
|
- tftp-server
|
||||||
|
- dhcp-server
|
||||||
Reference in New Issue
Block a user